From 370c8cd9a81d9c34890f2263613b7d12092e2032 Mon Sep 17 00:00:00 2001 From: Krishnan Mahadevan Date: Thu, 19 May 2022 08:54:15 +0530 Subject: [PATCH] Fix Files.copy() such that parent dirs are created Closes #2761 Also toggled the version back to a bug fix version --- CHANGES.txt | 3 + gradle.properties | 2 +- .../main/java/org/testng/JarFileUtils.java | 1 + .../java/org/testng/reporters/jq/Main.java | 4 +- .../java/org/testng/JarFileUtilsTest.java | 2 +- .../src/test/java/test/cli/CliTest.java | 64 +++++++++++++++++++ .../commandline/CommandLineOverridesXml.java | 2 +- .../test/mannotation/issue1976/IssueTest.java | 12 +--- .../test/java/testhelper/CompiledCode.java | 17 ++++- .../JarCreator.java | 2 +- .../test/java/testhelper/SimpleCompiler.java | 14 +++- .../src/test/java/testhelper/SourceCode.java | 34 ++++++++-- .../java/testhelper/TestClassGenerator.java | 39 +++++++++++ .../testhelper/TestNGSimpleClassLoader.java | 36 +++++++++++ 14 files changed, 209 insertions(+), 23 deletions(-) rename testng-core/src/test/java/{org/testng/jarfileutils => testhelper}/JarCreator.java (98%) create mode 100644 testng-core/src/test/java/testhelper/TestClassGenerator.java create mode 100644 testng-core/src/test/java/testhelper/TestNGSimpleClassLoader.java diff --git a/CHANGES.txt b/CHANGES.txt index 4c0e273c05..fd5f8ae525 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,7 @@ Current + +7.6.1 +Fixed: GITHUB-2761: Exception: ERROR java.nio.file.NoSuchFileException: /tmp/testngXmlPathInJar-15086412835569336174 (Krishnan Mahadevan) 7.6.0 Fixed: GITHUB-2741: Show fully qualified name of the test instead of just the function name for better readability of test output.(Krishnan Mahadevan) Fixed: GITHUB-2725: Honour custom attribute values in TestNG default reports (Krishnan Mahadevan) diff --git a/gradle.properties b/gradle.properties index 554181f1fe..3323d24fa1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ kotlin.code.style=official # Note: testng.kotlin-library.gradle.kts adds kotlin-stdlib for testImplementation kotlin.stdlib.default.dependency=false -testng.version=7.7.0 +testng.version=7.6.1 group=org.testng diff --git a/testng-core/src/main/java/org/testng/JarFileUtils.java b/testng-core/src/main/java/org/testng/JarFileUtils.java index 86ad2fbbbe..87237dfdd7 100644 --- a/testng-core/src/main/java/org/testng/JarFileUtils.java +++ b/testng-core/src/main/java/org/testng/JarFileUtils.java @@ -76,6 +76,7 @@ private boolean testngXmlExistsInJar(File jarFile, List classes) throws if (Parser.canParse(jeName.toLowerCase())) { InputStream inputStream = jf.getInputStream(je); File copyFile = new File(file, jeName); + copyFile.getParentFile().mkdirs(); Files.copy(inputStream, copyFile.toPath()); if (matchesXmlPathInJar(je)) { suitePath = copyFile.toString(); diff --git a/testng-core/src/main/java/org/testng/reporters/jq/Main.java b/testng-core/src/main/java/org/testng/reporters/jq/Main.java index a020810b97..b0bc7ba093 100644 --- a/testng-core/src/main/java/org/testng/reporters/jq/Main.java +++ b/testng-core/src/main/java/org/testng/reporters/jq/Main.java @@ -90,7 +90,9 @@ public void generateReport( if (is == null) { throw new AssertionError("Couldn't find resource: " + fileName); } - java.nio.file.Files.copy(is, new File(outputDirectory, fileName).toPath()); + File fileToCopy = new File(outputDirectory, fileName); + fileToCopy.getParentFile().mkdirs(); + java.nio.file.Files.copy(is, fileToCopy.toPath()); } } all = Files.readFile(header); diff --git a/testng-core/src/test/java/org/testng/JarFileUtilsTest.java b/testng-core/src/test/java/org/testng/JarFileUtilsTest.java index 471356f752..b207f4cb54 100644 --- a/testng-core/src/test/java/org/testng/JarFileUtilsTest.java +++ b/testng-core/src/test/java/org/testng/JarFileUtilsTest.java @@ -14,11 +14,11 @@ import java.util.List; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; -import org.testng.jarfileutils.JarCreator; import org.testng.xml.IPostProcessor; import org.testng.xml.XmlClass; import org.testng.xml.XmlSuite; import org.testng.xml.XmlTest; +import testhelper.JarCreator; public class JarFileUtilsTest { private static File jar = null; diff --git a/testng-core/src/test/java/test/cli/CliTest.java b/testng-core/src/test/java/test/cli/CliTest.java index 975de67b4e..4b339b23c5 100644 --- a/testng-core/src/test/java/test/cli/CliTest.java +++ b/testng-core/src/test/java/test/cli/CliTest.java @@ -2,10 +2,19 @@ import static org.assertj.core.api.Assertions.assertThat; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.List; import java.util.function.Supplier; +import java.util.stream.Collectors; import org.testng.Assert; import org.testng.CommandLineArgs; +import org.testng.IInvokedMethod; +import org.testng.IInvokedMethodListener; +import org.testng.ITestResult; import org.testng.TestNG; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; @@ -15,6 +24,12 @@ import test.cli.github1517.TestClassWithConfigSkipAndFailureSample; import test.cli.github1517.TestClassWithConfigSkipSample; import test.cli.github2693.TestClassSample; +import testhelper.CompiledCode; +import testhelper.JarCreator; +import testhelper.SimpleCompiler; +import testhelper.SourceCode; +import testhelper.TestClassGenerator; +import testhelper.TestNGSimpleClassLoader; public class CliTest extends SimpleBaseTest { @BeforeMethod @@ -70,6 +85,55 @@ public Object[][] getData() { }; } + @Test(description = "GITHUB-2761") + public void testToensureSuitesInJarAreExecutedViaCli() throws IOException { + List sources = + TestClassGenerator.generate( + "com.kungfu" + ".panda", Arrays.asList("DragonWarrior", "Tigress")); + List compiledSources = + SimpleCompiler.compileSourceCode(sources.toArray(new SourceCode[0])); + TestNGSimpleClassLoader classLoader = + new TestNGSimpleClassLoader(TestClassGenerator.getProjectDir()); + Class[] classes = + compiledSources.stream() + .map(compiledCode -> compile(classLoader, compiledCode)) + .collect(Collectors.toUnmodifiableList()) + .toArray(new Class[0]); + File jar = JarCreator.generateJar(classes); + LogInvocations logInvocations = new LogInvocations(); + TestNG testng = new TestNG(); + testng.addClassLoader(classLoader); + testng.setTestJar(jar.getAbsolutePath()); + testng.addListener(logInvocations); + testng.setVerbose(2); + testng.run(); + assertThat(testng.getStatus()).isEqualTo(0); + assertThat(logInvocations.logs) + .containsExactlyInAnyOrder( + "com.kungfu.panda.DragonWarrior.testMethod", "com.kungfu.panda.Tigress.testMethod"); + } + + private static Class compile(TestNGSimpleClassLoader loader, CompiledCode code) { + try { + return loader.injectByteCode(code); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + public static class LogInvocations implements IInvokedMethodListener { + private final List logs = new ArrayList<>(); + + @Override + public void beforeInvocation(IInvokedMethod method, ITestResult testResult) { + logs.add(method.getTestMethod().getQualifiedName()); + } + + public List getLogs() { + return logs; + } + } + public static class CustomTestNG extends TestNG { @Override diff --git a/testng-core/src/test/java/test/commandline/CommandLineOverridesXml.java b/testng-core/src/test/java/test/commandline/CommandLineOverridesXml.java index b647dd01e3..cd55434469 100644 --- a/testng-core/src/test/java/test/commandline/CommandLineOverridesXml.java +++ b/testng-core/src/test/java/test/commandline/CommandLineOverridesXml.java @@ -14,13 +14,13 @@ import org.testng.TestListenerAdapter; import org.testng.TestNG; import org.testng.annotations.Test; -import org.testng.jarfileutils.JarCreator; import org.testng.xml.XmlSuite; import test.SimpleBaseTest; import test.TestHelper; import test.commandline.issue341.LocalLogAggregator; import test.commandline.issue341.TestSampleA; import test.commandline.issue341.TestSampleB; +import testhelper.JarCreator; public class CommandLineOverridesXml extends SimpleBaseTest { diff --git a/testng-core/src/test/java/test/mannotation/issue1976/IssueTest.java b/testng-core/src/test/java/test/mannotation/issue1976/IssueTest.java index fb41feb58a..044a4325c0 100644 --- a/testng-core/src/test/java/test/mannotation/issue1976/IssueTest.java +++ b/testng-core/src/test/java/test/mannotation/issue1976/IssueTest.java @@ -11,27 +11,19 @@ import testhelper.CompiledCode; import testhelper.SimpleCompiler; import testhelper.SourceCode; +import testhelper.TestNGSimpleClassLoader; public class IssueTest extends ClassLoader { private static final File dir = SimpleCompiler.createTempDir(); - private static final class MyClassLoader extends ClassLoader { - - public Class injectByteCode(CompiledCode byteCode) throws ClassNotFoundException { - Class clazz = - defineClass(byteCode.getName(), byteCode.getByteCode(), 0, byteCode.getByteCode().length); - return loadClass(clazz.getName()); - } - } - @Test( dataProvider = "dp", expectedExceptions = TypeNotPresentException.class, description = "GITHUB-1976") public void testMethod(SourceCode... sources) throws IOException, ClassNotFoundException { TestNG tng = new TestNG(false); - MyClassLoader classLoader = new MyClassLoader(); + TestNGSimpleClassLoader classLoader = new TestNGSimpleClassLoader(); tng.addClassLoader(classLoader); List byteCodes = SimpleCompiler.compileSourceCode(sources); List> classes = Lists.newArrayList(); diff --git a/testng-core/src/test/java/testhelper/CompiledCode.java b/testng-core/src/test/java/testhelper/CompiledCode.java index 4c75c7a29f..3345faf3d7 100644 --- a/testng-core/src/test/java/testhelper/CompiledCode.java +++ b/testng-core/src/test/java/testhelper/CompiledCode.java @@ -11,9 +11,22 @@ public class CompiledCode { private final String name; public CompiledCode(String name, File directory, boolean skipLoading) throws IOException { + this("", name, directory, skipLoading); + } + + public CompiledCode(String packageName, String name, File directory, boolean skipLoading) + throws IOException { this.skipLoading = skipLoading; - this.name = name; - File classFile = new File(directory, name + Kind.CLASS.extension); + if (packageName != null && !packageName.trim().isEmpty()) { + this.name = packageName + "." + name; + } else { + this.name = name; + } + String location = name; + if (packageName != null && !packageName.trim().isEmpty()) { + location = packageName.replaceAll("\\Q.\\E", "/") + "/" + name; + } + File classFile = new File(directory, location + Kind.CLASS.extension); this.byteCode = Files.readAllBytes(classFile.toPath()); } diff --git a/testng-core/src/test/java/org/testng/jarfileutils/JarCreator.java b/testng-core/src/test/java/testhelper/JarCreator.java similarity index 98% rename from testng-core/src/test/java/org/testng/jarfileutils/JarCreator.java rename to testng-core/src/test/java/testhelper/JarCreator.java index 5a1c78f2e7..34aed543b5 100644 --- a/testng-core/src/test/java/org/testng/jarfileutils/JarCreator.java +++ b/testng-core/src/test/java/testhelper/JarCreator.java @@ -1,4 +1,4 @@ -package org.testng.jarfileutils; +package testhelper; import java.io.File; import java.io.IOException; diff --git a/testng-core/src/test/java/testhelper/SimpleCompiler.java b/testng-core/src/test/java/testhelper/SimpleCompiler.java index 6794d74e11..b8205d0708 100644 --- a/testng-core/src/test/java/testhelper/SimpleCompiler.java +++ b/testng-core/src/test/java/testhelper/SimpleCompiler.java @@ -25,8 +25,18 @@ public static List compileSourceCode(SourceCode... sources) throws List compiledCodes = Lists.newArrayList(); for (SourceCode source : sources) { source.getLocation().delete(); - CompiledCode compiledCode = - new CompiledCode(source.getName(), source.getDirectory(), source.isSkipLoading()); + CompiledCode compiledCode; + if (source.hasPackageName()) { + compiledCode = + new CompiledCode( + source.getPackageName(), + source.getName(), + source.getDirectory(), + source.isSkipLoading()); + } else { + compiledCode = + new CompiledCode(source.getName(), source.getDirectory(), source.isSkipLoading()); + } compiledCodes.add(compiledCode); } return compiledCodes; diff --git a/testng-core/src/test/java/testhelper/SourceCode.java b/testng-core/src/test/java/testhelper/SourceCode.java index 18fc570863..7d81f44b2c 100644 --- a/testng-core/src/test/java/testhelper/SourceCode.java +++ b/testng-core/src/test/java/testhelper/SourceCode.java @@ -3,11 +3,12 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; -import java.nio.file.StandardOpenOption; +import java.nio.file.Path; import javax.tools.JavaFileObject.Kind; public class SourceCode { + private final String packageName; private final String name; private final File directory; private final boolean skipLoading; @@ -15,14 +16,31 @@ public class SourceCode { public SourceCode(String name, String src, File directory, boolean skipLoading) throws IOException { + this("", name, src, directory, skipLoading); + } + + public SourceCode( + String packageName, String name, String src, File directory, boolean skipLoading) + throws IOException { + this.packageName = packageName; this.name = name; this.directory = directory; this.skipLoading = skipLoading; - this.location = new File(directory, name + Kind.SOURCE.extension); - if (this.location.exists()) { + String path = name; + boolean includesPackageName = false; + if (packageName != null && !packageName.trim().isEmpty()) { + path = packageName.replaceAll("\\Q.\\E", "/") + "/" + name; + includesPackageName = true; + } + this.location = new File(directory, path + Kind.SOURCE.extension); + if (!includesPackageName && this.location.exists()) { this.location.delete(); } - Files.write(location.toPath(), src.getBytes(), StandardOpenOption.CREATE_NEW); + Path parentDir = this.location.getParentFile().toPath(); + if (!Files.exists(parentDir)) { + Files.createDirectories(parentDir); + } + Files.write(location.toPath(), src.getBytes()); } public File getDirectory() { @@ -33,6 +51,14 @@ public String getName() { return name; } + public boolean hasPackageName() { + return packageName != null && !packageName.trim().isEmpty(); + } + + public String getPackageName() { + return packageName; + } + public boolean isSkipLoading() { return skipLoading; } diff --git a/testng-core/src/test/java/testhelper/TestClassGenerator.java b/testng-core/src/test/java/testhelper/TestClassGenerator.java new file mode 100644 index 0000000000..114eeb6f72 --- /dev/null +++ b/testng-core/src/test/java/testhelper/TestClassGenerator.java @@ -0,0 +1,39 @@ +package testhelper; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; + +public final class TestClassGenerator { + private static final File projectDir = SimpleCompiler.createTempDir(); + + private TestClassGenerator() { + // Utility class. Defeat instantiation. + } + + public static File getProjectDir() { + return projectDir; + } + + public static List generate(String packageName, List classNames) { + return classNames.stream() + .map(className -> generateCode(packageName, className)) + .collect(Collectors.toUnmodifiableList()); + } + + private static SourceCode generateCode(String packageName, String className) { + String source = "package " + packageName + ";\n\n"; + source += "import org.testng.annotations.Test;\n"; + source += "public class " + className + " {\n"; + source += " @Test\n"; + source += " public void testMethod() {\n"; + source += " }\n"; + source += "}\n"; + try { + return new SourceCode(packageName, className, source, projectDir, false); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/testng-core/src/test/java/testhelper/TestNGSimpleClassLoader.java b/testng-core/src/test/java/testhelper/TestNGSimpleClassLoader.java new file mode 100644 index 0000000000..ff7592c79c --- /dev/null +++ b/testng-core/src/test/java/testhelper/TestNGSimpleClassLoader.java @@ -0,0 +1,36 @@ +package testhelper; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; + +public final class TestNGSimpleClassLoader extends ClassLoader { + + private final File baseDir; + + public TestNGSimpleClassLoader() { + this(null); + } + + public TestNGSimpleClassLoader(File baseDir) { + this.baseDir = baseDir; + } + + public Class injectByteCode(CompiledCode byteCode) throws ClassNotFoundException { + Class clazz = + defineClass(byteCode.getName(), byteCode.getByteCode(), 0, byteCode.getByteCode().length); + return loadClass(clazz.getName()); + } + + @Override + protected URL findResource(String name) { + if (this.baseDir != null) { + try { + return new File(this.baseDir.getAbsolutePath() + "/" + name).toURI().toURL(); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + return super.findResource(name); + } +}