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 dynamically load dataprovider class
With current Data providers implementation, it's code will stick around in method area (JVM spec $2.5.4) for 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 and deleted all links to it Test dataprovider class unloaded by analysing memory dumps.
- Loading branch information
Dzmitry Sankouski
committed
Mar 12, 2022
1 parent
331bfbe
commit a0b7229
Showing
20 changed files
with
305 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
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
85 changes: 85 additions & 0 deletions
85
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,85 @@ | ||
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.annotations.Test | ||
import org.testng.dataprovider.sample.issue2724.TestClass | ||
import org.testng.dataprovider.sample.issue2724.TestDPUnloaded | ||
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(TestClass::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") | ||
System.setProperty("memdump.path", dumpPath) | ||
|
||
run(TestDPUnloaded::class.java) | ||
|
||
val heapDumpFile = File(dumpPath) | ||
assertThat(heapDumpFile).exists() | ||
val 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() | ||
} | ||
} | ||
|
||
fun getGCPath(instance: Instance) : String { | ||
var result = "" | ||
if (!instance.isGCRoot) { | ||
result += getGCPath(instance.nearestGCRootPointer) | ||
} | ||
return result + "${instance.javaClass.name}\n" | ||
} | ||
|
||
companion object { | ||
var classLoadCount = 0 | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
testng-core/src/test/kotlin/org/testng/dataprovider/sample/issue2724/ClassLoadedCounter.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,11 @@ | ||
package org.testng.dataprovider.sample.issue2724 | ||
|
||
import org.testng.dataprovider.DynamicDataProviderLoadingTest | ||
|
||
class ClassLoadedCounter { | ||
companion object { | ||
init { | ||
DynamicDataProviderLoadingTest.classLoadCount++ | ||
} | ||
} | ||
} |
14 changes: 14 additions & 0 deletions
14
testng-core/src/test/kotlin/org/testng/dataprovider/sample/issue2724/DataProviders.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,14 @@ | ||
package org.testng.dataprovider.sample.issue2724 | ||
|
||
import org.testng.annotations.DataProvider | ||
|
||
class DataProviders { | ||
@DataProvider | ||
fun data() : Array<Array<Any>> { | ||
return arrayOf( | ||
arrayOf("Mike", 34, "student"), | ||
arrayOf("Mike", 23, "driver"), | ||
arrayOf("Paul", 20, "director") | ||
) | ||
} | ||
} |
15 changes: 15 additions & 0 deletions
15
testng-core/src/test/kotlin/org/testng/dataprovider/sample/issue2724/TestClass.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,15 @@ | ||
package org.testng.dataprovider.sample.issue2724 | ||
|
||
import org.testng.annotations.Test | ||
|
||
class TestClass { | ||
|
||
@Suppress("UNUSED") | ||
@Test( | ||
dataProviderDynamicClass = "org.testng.dataprovider.sample.issue2724.DataProviders", | ||
dataProvider = "data" | ||
) | ||
fun testDynamicDataProvider(name: String, age: Int, status: String) { | ||
|
||
} | ||
} |
Oops, something went wrong.