The present invention relates to a method and system for testing computer programs. More specifically, the present invention is directed to a method and system for predicting memory leaks from unit testing.
When a dynamically allocated memory space is not properly deallocated, a memory leak takes place. As a result, the pointer or memory reference to the dynamically allocated memory space may not be reclaimed. Consequently, the allocable space of the total available memory will gradually diminish, causing the system to crash. Some programming languages include functions (for example Jvm in Java) to allocate memory from a “Java Heap” where the memory heap allocations and deallocations are hidden from the programmer. In this case, Java virtual machine (Jvm) executes the heap allocations when new objects, such as String XYZ=“XYZ”, are specified. Jvm uses the implied new constructor in this case, as it allocates the string “XYZ”. Jvm executes the deallocations when the program performs garbage collection and the object is no longer referenced. However, the programmer is usually not aware of the cost associated with the objects created and may not take care to eliminate references to objects that are not required because these allocations and deallocations are done by the Jvm. Nevertheless, memory leaks in JAVA also cause longer garbage collection times resulting in performance degradation.
There have been some suggested methods for improving the memory leak problem. However, they all lack the capability of predicting a memory leak situation without the memory leak occurring to a noticeable degree, during full program execution while under observation. Furthermore, none of the current solutions inherently distinguish between initialization memory allocation, memory caching, and bona fide memory leaks. The current methods typically let the user decide if allocated memory that cannot be garbage collected is indeed from a memory leak.
Therefore there is a need for a system and method for memory leak prediction in runtime execution environment that generates memory allocation and deallocation events by executing unit tests.
In one embodiment, the invention is a method, or system for predicting memory leak in a computer program. The invention includes acquiring a reference to a tested unit included in the computer program for preventing static data objects from being deallocated; repeatedly executing the tested unit for more than once; tracking which objects in the tested unit are allocated in a corresponding executing time; performing garbage collection; tracking which objects are deallocated during the garbage collection; comparing the object allocations to the object deallocations; and determining if every execution of the tested unit allocates memory that cannot be deallocated.
Garbage collection (GC) is a system of automatic memory management which seeks to reclaim memory used by objects which will never be referenced in the future. The part of a system which performs garbage collection is typically called a garbage collector (gc).
The basic principle of how a garbage collector works is to determine what data objects in a program cannot be referenced in the future, and then reclaim the storage used by those objects. Although in general, it is impossible to know the moment an object has been used for the last time, garbage collectors use conservative estimates that allow them to identify when an object could not possibly be referenced in the future. For example, if there are no references to an object in the system, then it can never be referenced again.
While GC assists the management of memory, the feature is also almost always necessary in order to make a programming language type safe, because it prevents several classes of runtime errors. For example, it prevents “dangling pointer” errors, where a reference to a deallocated object is used.
Used mainly in object-oriented programming, the term method refers to a piece of code that is exclusively associated either with a class (called class methods or static methods) or with an object (called instance methods). Like a procedure in procedural programming languages, a method usually includes a sequence of statements to perform an action, a set of input parameters to parameterize those actions, and possibly an output value (called return value) of some kind. The purpose of methods is to provide a mechanism for accessing (for both reading and writing) the private data stored in an object or a class.
Functional testing (similar to black-box testing) is the process of verifying that a system or system component adheres to the specification that defines its requirements. Functional testing can be performed at the system level or the unit level. To perform functional testing, one typically creates a set of input/outcome relationships that verify whether each specification requirement is implemented correctly. At least one test case should be created for each entry in the specification document. Preferably, these test cases should test the various boundary conditions for each entry. After the test suite is ready, the test cases are executed and verified.
Unit testing involves testing software code at its smallest functional point, which is typically a single class. Each individual class should be tested in isolation before it is tested with other units or as part of a module or application. By testing every unit individually, most of the errors that might be introduced into the code over the course of a project can be detected or prevented entirely. The objective of unit testing is to test not only the functionality of the code, but also to ensure that the code is structurally sound and robust, and is able to respond appropriately in all conditions. Performing unit testing reduces the amount of work needs to be done at the application level, and drastically reduces the potential for errors. However, unit testing can be quite labor intensive if performed manually. The key to conducting effective unit testing is automatically generating test cases.
By performing unit testing, one can avoid the dangers that follow from delaying testing until the end of development. Because the most critical dynamic problems in application software (such as performance problems) are often the result of design or implementation flaws, fixing these problems frequently requires redesigning and/or rewriting the entire application software. However, if one tests each servlet, bean, or other type of program unit immediately after it is written (i.e., perform unit testing), critical flaws can be spotted and resolved before they become widespread. Essentially, many problems can be prevented that would be difficult and costly to fix. This translates to fewer errors that elude testing, fewer resources spent on testing and debugging, and faster time to market.
In one embodiment, the invention disclosed in U.S. Pat. No. 5,784,553, the contents of which are herein fully incorporated by reference, is a Java unit testing tool that tests any Java class or component. That disclosed invention reads the specification information built into a class, then automatically creates and executes test cases that check the functionality described in the specification.
At the end of the program execution, the symbolic VM writes the selected input into the test suite database 126. The selected input contains the values for the arguments to the method being tested. The symbolic VM has the capability of changing on-the-fly the inputs it is using to run the test cases. The symbolic VM uses the information in the contracts that exist in the instrumented class file. The symbolic VM generates inputs to cover the different conditions that the DbC contracts specify. By generating inputs that cover the conditions in the @pre contract, only test cases that are valid are generated. By trying to generate inputs that cover the conditions in the @post, @invariant, @assert and in @pre conditions of called methods, the symbolic VM checks that the method follows the specification. For example, if the symbolic VM can find an input that makes a @post fail, it means that the method doesn't follow its DbC specification.
At this point, the driver program invokes the symbolic VM again. This process is repeated until the symbolic VM cannot find any more inputs. The runtime library 128 contains support code for the automatic stubs, the symbolic execution and the additional instrumented code generated by the invention. Stubs are basically replacements for references to methods external to the class.
In one embodiment, the present invention is a method and system for memory leak prediction in runtime execution environment that generates memory allocation and deallocation events by executing unit tests. The method and system of the present invention includes garbage collection functionality. The functionality that is natively supported by the runtime environment includes
Other functionalities of runtime environment may be either natively supported by the environment, or introduced by the invention. An example of such other functionalities includes reporting memory allocation events. Most target runtime systems support it natively, however, in absence of native support this can be achieve by instrumenting either source code or binary object code of the unit test.
An instance of such runtime environment is a virtual machine (VM). For simplicity reasons, most examples for describing the method and system of the present invention are given in VM environment, the method and system of the present invention is not limited to such environment. Other environments such as, IBM™ Java VM, Oberon™ interpreter, C# interpreter™, compiled code written in any language that satisfies the above condition, are also within the scope of the present invention.
Runtime System 1301 includes a VM or Interpreter 1302 that supports automatic garbage collection; a Unit Test Harness 1303, which is a program module or a stand-alone program responsible for setting up and adjusting test environment, exercising test cases, and collecting, processing, and reporting results to other programs and/or program modules; and a Data Acquisition Module 1304 responsible for acquiring data necessary for memory leak detection from runtime execution environment and passing it over to Data Processor Abstraction Layer. In the case of a VM configuration, this module is responsible for enabling and disabling various VM debugger and profiler interface events, and/or instrumented statements in the unit tests, and passing this information to Data Processor Abstraction Layer 1305. Data Processor Abstraction Layer 1305 is a program module(s) responsible for extracting information from runtime environment (e.g., specific events), recording this information into runtime environment-independent data structures (events), and sending them to the Data Processor 1306, described below. For example, a format of the “method entered” event varies widely depending on internal implementation of a particular VM or an interpreter it was generated by. Data Processor Abstraction Layer extracts information useful for the Data Processor 1306 in a platform-dependent way, and sends it to Data Processor using a platform-independent data structure.
Data Processor 1306 is responsible for recording data that is provided by the runtime system to the database, interpreting flow control events, analyzing and interpreting the data, and reporting it to Report Consumer on demand. Data is reported in platform-independent way. Data Processor may be implemented as a module running on the same VM as a runtime system, or it may be running within a different application, or on a different computer than runtime system.
In one embodiment, Data Processor 1306 includes a Data Collection Module 1307, an object allocation and deallocation database 1308, a Data Analysis Module 1309, and a Report Generator 1310. Data Collection Module 1307 is responsible for transcribing Data Acquisition Module events and for storing them in the database. Object allocation and deallocation database 1308 contains information pertinent to objects allocation and deallocation as well as information on execution context in which those allocation/deallocation events transpired. Data Analysis Module 1309 retrieves information from the database 1308, analyzes and interprets the data, and sends the results to the Report Generator 1310. Report Generator requests Data Analysis Module to perform specific types of analysis, receives and formats the results, links the results to optional information from the database (e.g., stack traces and source code line numbers at which memory allocation happened, etc.), and sends the resulting report to the Memory Leaks Report Abstraction Layer 1312, or Report Consumer 1311.
Report Consumer 1311 requests, receives, and makes use of reports from Data Processor 1306. Data Processor may be implemented as a subsystem of the Report Consumer or vice versa. Report Consumer 1311 includes a Memory Leaks Report Abstraction Layer 1312 for allowing the Report Consumer to request and receive memory leaks reports. Memory Leaks Report Abstraction Layer 1312 also allows Report Consumer to access data in the report in implementation-independent way.
Typically, Design by Contract (DbC) is defined as a formal way of using comments to incorporate specification information into the code itself. DbC was design to create a contract between a piece of code and its caller. This contract specifies what the callee expects and what the caller can expect. Basically, the code specification is expressed unambiguously using a formal language that describes the code's implicit contracts. The term unit testing is used here to describe testing the smallest possible unit of an application program. For example, in terms of Java, unit testing involves testing a class as soon as it is compiled. Users can use DbC comments to filter out error messages that are not relevant to the class under test. For example, if an expected exception in the code is documented using the @exception tag, any occurrence of that particular exception may be suppressed. If a permissible range for valid method inputs using the @pre tag is documented, any errors found for inputs that do not satisfy those preconditions may be suppressed.
In one embodiment, during execution of unit tests, a test harness (e.g.,
As shown in block 204, the invention then repeatedly executes the same unit test (for example, a minimum of three times) to ensure that specific objects are allocated, as a result of calling a given tested method. The repeated execution is optional to better predict memory leak in each execution teration.
Typically, there are at least two types of memory management/optimization practices that can be misidentified as “memory leaks,” unless a test case is executed a minimum of twice and memory allocations are tracked with regards to each repetition. For example, in the “lazy initialization” pattern depicted in
As shown in
The example in
In the second scenario, ATest.testInit1( ) is called by test harness twice. A first call is made to ATest.testInit1( ), and A.init( ) is invoked for a first time, as shown in block 802. In block 804, “_lazy” is assigned “Object” instance # 1, a second call is made to ATest.testInit1( ), and A.init( ) is invoked for a second time, as shown in block 806. In block 808, “_lazy” !=null, init( ) returns right away, wile no new objects are allocated.
In block 809, at the end of the test sequence, Data Processor analyses memory use for A.init( ). In the first repetition, memory allocated for type Object is X1 bytes. In the second repetition, memory allocated for type Object is 0 bytes. After garbage collection, memory allocated for type Object in the first repetition was not garbage-collected. As a result, the amount of outstanding memory did not increase between the two repetitions, memory was only allocated in the first repetition, no memory is allocated after first repetition, no other memory is allocated, and there is no memory leak.
Likewise, as shown in
In the first repetition, memory allocated for type String is X1 bytes. After garbage collection, memory allocated for type String in the first repetition was not garbage-collected, and the amount of memory allocated for type String increased by X1 bytes. Therefore, the amount of outstanding memory increases after calling A.setX(String str) by X1, and there may be a memory leak.
In the second repetition, ATest.testSetX1( ) is called from the test harness twice. A first call is made to ATest.testSetX1( ), and new A( ).setX(“hello”) is called for the first time, in block 902. In block 904, _x is assigned an instance #1 of the String “hello.” A second call is made to ATest.testSetX1( ), and new A( ).setX(“hello”) is called for the second time, as shown in block 906. In block 907, _x is assigned an instance #2 of the String “hello”, and the reference to an instance #1 of the String “hello” is released. The instance #1 can now be garbage-collected.
In block 908, at the end of the test sequence, Data Processor analyzes memory use for A.setX(String str). In the first repetition, memory allocated for type String is X1 bytes, and in the second repetition, memory allocated for type String is X1 bytes. After garbage collection, all memory allocated for type String in the first repetition was garbage-collected, however, the memory allocated for type String in the second repetition was not garbage-collected. Also, the amount of memory allocated for type String in the first repetition equals to that allocated for type String in the second repetition. Consequently, the amount of outstanding memory did not increase between the two repetitions. Moreover, all memory allocated for type String in the previous repetition is marked to be garbage-collected after the following repetition, and thus no other memory is allocated, and there is no memory leak.
Similarly, there is at least one type of memory management/optimization practice that can be misidentified as “memory leak,” unless the test case is executed a minimum of three times or memory allocations are tracked with regards to each repetition. For example, as shown in
In block 1105, at the end of the test sequence, Data Processor analyzes memory use for A.foo( ). In the first repetition, memory allocated for type String is 2×S1 bytes. After garbage collection, memory allocated for type String in the first repetition was not garbage-collected, and the amount of memory allocated for type String increased by 2×X1 bytes. As a result, The amount of outstanding memory increases by 2×S1, after calling A.foo( ), and there may be a memory leak.
In the second scenario shown in
In case of third scenario shown in
In block 1207, at the end of the test sequence, Data Processor analyzes memory use for A.foo( ). For the first repetition, memory allocated for type String is 2×S1 bytes. Likewise, for the second repetition, memory allocated for type String is 1×S1 bytes, and for the third repetition, memory allocated for type String is 1×S1 bytes. After garbage collection, the 1×S1 bytes of memory allocated for type String in the first repetition the 0×S1 bytes of memory allocated in for type String in the second repetition and the 1×S1 bytes of memory allocated in for type String in the third repetition are not garbage-collected. Accordingly, the amount of outstanding memory does not increase after calling A.foo( ) three times, no other memory is allocated, and there is no memory leak.
The invention then tracks which objects are allocated by which repetition, as illustrated in block 206.
As shown in
Data Processor Abstraction Layer 403 passes data into Data Collection Module 405 of Data Processor. Data Collection Module 405 receives “test method entered” events. In block 407, if the method is the same as in previous repetition, Data Collector Module increases repetition counter in block 408 else, Data Collector Module records method data to the database, resets repetition counter, and resets current test method info, as shown in block 406.
Allocation data record 505 includes a unique object ID, and tested method information. For example, for Java this information includes a fully qualified method name and parameter types, optionally, call trace, argument values and runtime types, return values, and runtime types. Allocation data record 505 also includes test method information, repetition number, allocated object type, and information on where the object was allocated from, such as, line number int test method, line number in tested method, method name, and allocation stack trace.
Deallocation data 507 includes a unique object ID, object type, a repetition number at which the object reference was released (optional), and an object allocation and deallocation database 508.
As shown, tested method exit event informs the Data Processor that the following object allocations are not side effects of the execution of the tested methods. Information (e.g., blocks 505, 507, in
In one embodiment, all the objects allocated from a particular tested method have corresponding database entries in relation to: the tested method from which they were allocated; test method from which the tested method was invoked; repetition number in which they were allocated and recorded in the database; object type; object instance unique ID; and object size.
Referring back to
In block 210, the invention releases a reference to the tested unit (also, see
For example, if type “A” is being tested (tested unit), a reference to an object of type “A” created by the test harness is released. In Java, this can be done as follows:
At this point, object deallocation information is not added to the database, since the objects deallocated after the above reference was freed and before the reference to the next test object is acquired will be the suspected leaked objects.
In block 212, the invention tracks which objects are deallocated during garbage collection. The objects that have deallocation record (block 505 in
In block 601, for each object allocation of type “X” within the results of the request, Data Analysis Module requests from the database information on deallocations of the objects with same object IDs as in type “X”. Deallocated objects are counted if they were deallocated as a side effect of executing test case with test method info “Y” (e.g., test method info for ATest.testFoo1( ), in
In block 604, the difference between objects allocations and deallocations in blocks 601 and 603 is calculated and reported to a Report Generator 605 as a memory leak (unless one of the additional filtering algorithms determines that there is no leak (e.g.,
For each object allocation of each data type, for example data type “A”, in the data requested above (block 701), Data Analysis Module requests from the database deallocation information (block 703). Deallocated objects are counted in, if they were deallocated, as a result of executing tested method with info record “Z” (e.g., tested method info for A.foo( ),
In one embodiment, the invention tracks object deallocations when garbage collection has not been explicitly requested. The garbage collection can be performed at any point, because the deallocation events (related to the test and tested method information) are stored in the database continuously, as soon as the VM reports that it has entered tested method.
In one embodiment, the invention computes metrics on the allocated and deallocated memory, based on database entries for memory allocation/deallocations, as shown in
In one embodiment, the invention computes gross memory leaks as a difference between all the memory allocated and deallocated, while executing the tested method. The amount of allocated memory is computed as the sum of all allocated memory for that type of metric. The amount of deallocated memory is computed as the sum of all deallocated memory for that type of metric.
The invention is also capable of computing information derived from database. For instance, the sum of the number of all the objects allocated as a result of executing a given program unit and the sum of the number of all the objects leaked can be obtained when executing the program unit, as shown in
In one embodiment, the invention tracks the class of each object by adding type information for each memory allocation database entry, as shown in
The invention is also capable of tracking the function, the method, or the subroutine being executed. For example, for every method invoked when the tested method is being executed, the method's arguments values and return value(s), and the position of the method in the stack trace that lead to a given memory allocation can be tracked. Also, functions, methods, and/or subroutines that are filtered according to a specified filter can be tracked. For instance, the invention is capable of tracking methods only if they are filtered in, or not filtered out.
In one embodiment, the invention tracks the line of source code being executed by enabling line execution events, and adding this information to “Allocation Data” in the corresponding database entry for memory allocation, as shown in
In one embodiment, the invention tracks the call stack during memory allocation and records the call stack that leads to a memory allocation in the database. The invention also tracks the call stack with line number information.
In one embodiment, the testing framework of the invention prevents static data objects from being deallocated by keeping a reference to the tested unit, as shown in
It will be recognized by those skilled in the art that various modifications may be made to the illustrated and other embodiments of the invention described above, without departing from the broad inventive scope thereof. It will be understood therefore that the invention is not limited to the particular embodiments or arrangements disclosed, but is rather intended to cover any changes, adaptations or modifications which are within the scope of the appended claims. For example, although the present invention is described in conjunction with Java, any other language that supports automatic garbage collection can be unit tested and its memory leaks predicted using the present invention.