-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
DataProvider: possibility to unload dataprovider class, when done wit…
…h it With current Data providers implementation, it's code will stick around in method area (JVM spec $2.5.4) for the entire test run. By specifying dataprovider class with it's full qualified name, and by using new custom classloader to load it, when needed, JVM gets a chance to unload dataprovider class, when we're done with it. Testing dataprovider class unload is performed by analysing memory dumps. Also, there is a test(comparePerformanceAgainstCsvFiles) to measure performance of data as code approach against common data approch, where data is stored in csv files.
- Loading branch information
1 parent
880801e
commit aae0724
Showing
24 changed files
with
410 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
43 changes: 43 additions & 0 deletions
43
testng-core/src/main/java/org/testng/internal/DataProviderLoader.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package org.testng.internal; | ||
|
||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import org.testng.log4testng.Logger; | ||
|
||
public class DataProviderLoader extends ClassLoader { | ||
private static final int BUFFER_SIZE = 1 << 20; | ||
private static final Logger log = Logger.getLogger(DataProviderLoader.class); | ||
|
||
public Class loadClazz(String path) throws ClassNotFoundException { | ||
Class clazz = findLoadedClass(path); | ||
if (clazz == null) { | ||
byte[] bt = loadClassData(path); | ||
clazz = defineClass(path, bt, 0, bt.length); | ||
} | ||
|
||
return clazz; | ||
} | ||
|
||
private byte[] loadClassData(String className) throws ClassNotFoundException { | ||
InputStream in = | ||
this.getClass() | ||
.getClassLoader() | ||
.getResourceAsStream(className.replace(".", "/") + ".class"); | ||
if (in == null) { | ||
throw new ClassNotFoundException("Cannot load resource input stream: " + className); | ||
} | ||
|
||
byte[] classBytes; | ||
try { | ||
classBytes = in.readAllBytes(); | ||
} catch (IOException e) { | ||
throw new ClassNotFoundException("ERROR reading class file" + e); | ||
} | ||
|
||
if (classBytes == null) { | ||
throw new ClassNotFoundException("Cannot load class: " + className); | ||
} | ||
|
||
return classBytes; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
20 changes: 20 additions & 0 deletions
20
testng-core/src/main/java/org/testng/internal/DataProviderMethodRemovable.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package org.testng.internal; | ||
|
||
import java.lang.reflect.Method; | ||
import org.testng.annotations.IDataProviderAnnotation; | ||
|
||
/** Represents an @{@link org.testng.annotations.DataProvider} annotated method. */ | ||
class DataProviderMethodRemovable extends DataProviderMethod { | ||
|
||
DataProviderMethodRemovable(Object instance, Method method, IDataProviderAnnotation annotation) { | ||
super(instance, method, annotation); | ||
} | ||
|
||
public void setInstance(Object instance) { | ||
this.instance = instance; | ||
} | ||
|
||
public void setMethod(Method method) { | ||
this.method = method; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
126 changes: 126 additions & 0 deletions
126
testng-core/src/test/kotlin/org/testng/dataprovider/DynamicDataProviderLoadingTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package org.testng.dataprovider | ||
|
||
import org.assertj.core.api.Assertions.assertThat | ||
import org.assertj.core.api.SoftAssertions | ||
import org.netbeans.lib.profiler.heap.HeapFactory2 | ||
import org.netbeans.lib.profiler.heap.Instance | ||
import org.netbeans.lib.profiler.heap.JavaClass | ||
import org.testng.Reporter | ||
import org.testng.annotations.Test | ||
import org.testng.dataprovider.sample.issue2724.* | ||
import test.SimpleBaseTest | ||
import java.io.File | ||
import java.nio.file.Files | ||
|
||
const val CLASS_NAME_DP = "org.testng.dataprovider.sample.issue2724.DataProviders" | ||
const val CLASS_NAME_DP_LOADER = "org.testng.internal.DataProviderLoader" | ||
|
||
class DynamicDataProviderLoadingTest : SimpleBaseTest() { | ||
|
||
@Test | ||
fun testDynamicDataProviderPasses() { | ||
val listener = run(SampleDynamicDP::class.java) | ||
assertThat(listener.failedMethodNames).isEmpty() | ||
assertThat(listener.succeedMethodNames).containsExactly( | ||
"testDynamicDataProvider(Mike,34,student)", | ||
"testDynamicDataProvider(Mike,23,driver)", | ||
"testDynamicDataProvider(Paul,20,director)", | ||
) | ||
assertThat(listener.skippedMethodNames).isEmpty() | ||
} | ||
|
||
@Test | ||
fun testDynamicDataProviderUnloaded() { | ||
val tempDirectory = Files.createTempDirectory("temp-testng-") | ||
val dumpPath = "%s/%s".format(tempDirectory.toAbsolutePath().toString(), "dump.hprof") | ||
val dumpPathBeforeSample = | ||
"%s/%s".format(tempDirectory.toAbsolutePath().toString(), "dump-before-sample.hprof") | ||
System.setProperty("memdump.path", dumpPath) | ||
|
||
saveMemDump(dumpPathBeforeSample) | ||
val heapDumpBeforeSampleFile = File(dumpPathBeforeSample) | ||
assertThat(heapDumpBeforeSampleFile).exists() | ||
var heap = HeapFactory2.createHeap(heapDumpBeforeSampleFile, null) | ||
val beforeSampleDPClassDump: JavaClass? = heap.getJavaClassByName(CLASS_NAME_DP) | ||
assertThat(beforeSampleDPClassDump) | ||
.describedAs( | ||
"Class $CLASS_NAME_DP shouldn't be loaded, before test sample started. " | ||
) | ||
.isNull() | ||
|
||
run(SampleDPUnloaded::class.java) | ||
|
||
val heapDumpFile = File(dumpPath) | ||
assertThat(heapDumpFile).exists() | ||
heap = HeapFactory2.createHeap(heapDumpFile, null) | ||
|
||
with(SoftAssertions()) { | ||
val dpLoaderClassDump: JavaClass? = heap.getJavaClassByName(CLASS_NAME_DP_LOADER) | ||
val dpClassDump: JavaClass? = heap.getJavaClassByName(CLASS_NAME_DP) | ||
val dpLoaderMessage = dpLoaderClassDump?.instances?.joinToString("\n") { | ||
getGCPath(it) | ||
} | ||
val dpMessage = dpLoaderClassDump?.instances?.joinToString("\n") { | ||
getGCPath(it) | ||
} | ||
|
||
this.assertThat(dpLoaderClassDump?.instances) | ||
.describedAs( | ||
""" | ||
All instances of class $CLASS_NAME_DP_LOADER should be garbage collected, but was not. | ||
Path to GC root is: | ||
$dpLoaderMessage | ||
""".trimIndent() | ||
) | ||
.isEmpty() | ||
this.assertThat(dpClassDump) | ||
.describedAs( | ||
""" | ||
Class $CLASS_NAME_DP shouldn't be loaded, but it was. | ||
Path to GC root is: | ||
$dpMessage | ||
""".trimIndent() | ||
) | ||
.isNull() | ||
this.assertAll() | ||
} | ||
} | ||
|
||
@Test | ||
fun comparePerformanceAgainstCsvFiles() { | ||
val simpleDPSuite = create().apply { | ||
setTestClasses(arrayOf(SampleSimpleDP::class.java)) | ||
setListenerClasses(listOf(TestTimeListener::class.java)) | ||
} | ||
val csvSuite = create().apply { | ||
setTestClasses(arrayOf(SampleWithCSVData::class.java)) | ||
setListenerClasses(listOf(TestTimeListener::class.java)) | ||
} | ||
val dataAsCodeSuite = create().apply { | ||
setTestClasses(arrayOf(SampleDynamicDP::class.java)) | ||
setListenerClasses(listOf(TestTimeListener::class.java)) | ||
} | ||
|
||
Reporter.log("Test execution time:\n") | ||
for (suite in listOf( | ||
Pair("simple dataprovider", simpleDPSuite), | ||
Pair("dataprovider as code", dataAsCodeSuite), | ||
Pair("csv dataprovider", csvSuite), | ||
)) { | ||
run(false, suite.second) | ||
Reporter.log( | ||
"${suite.first} execution times: %d milliseconds." | ||
.format(TestTimeListener.testRunTime), | ||
true | ||
) | ||
} | ||
} | ||
|
||
fun getGCPath(instance: Instance): String { | ||
var result = "" | ||
if (!instance.isGCRoot) { | ||
result += getGCPath(instance.nearestGCRootPointer) | ||
} | ||
return result + "${instance.javaClass.name}\n" | ||
} | ||
} |
Oops, something went wrong.