Skip to content

Commit

Permalink
Fix Files.copy() such that parent dirs are created
Browse files Browse the repository at this point in the history
Closes #2761

Also toggled the version back to a bug fix version
  • Loading branch information
krmahadevan committed May 19, 2022
1 parent d51fbab commit b649672
Show file tree
Hide file tree
Showing 14 changed files with 204 additions and 23 deletions.
3 changes: 3 additions & 0 deletions 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)
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions testng-core/src/main/java/org/testng/JarFileUtils.java
Expand Up @@ -76,6 +76,7 @@ private boolean testngXmlExistsInJar(File jarFile, List<String> 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();
Expand Down
4 changes: 3 additions & 1 deletion testng-core/src/main/java/org/testng/reporters/jq/Main.java
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion testng-core/src/test/java/org/testng/JarFileUtilsTest.java
Expand Up @@ -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;
Expand Down
60 changes: 60 additions & 0 deletions testng-core/src/test/java/test/cli/CliTest.java
Expand Up @@ -2,10 +2,18 @@

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 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;
Expand All @@ -15,6 +23,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
Expand Down Expand Up @@ -70,6 +84,52 @@ public Object[][] getData() {
};
}

@Test(description = "GITHUB-2761")
public void testToEnsureSuitesInJarAreExecutedViaCli() throws IOException {
SourceCode[] sources =
TestClassGenerator.generate("com.kungfu.panda", Arrays.asList("DragonWarrior", "Tigress"));
List<CompiledCode> compiledSources = SimpleCompiler.compileSourceCode(sources);
TestNGSimpleClassLoader classLoader =
new TestNGSimpleClassLoader(TestClassGenerator.getProjectDir());
Class<?>[] classes =
compiledSources.stream()
.map(compiledCode -> compile(classLoader, compiledCode))
.toArray(size -> new Class<?>[size]);
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<String> logs = new ArrayList<>();

@Override
public void beforeInvocation(IInvokedMethod method, ITestResult testResult) {
logs.add(method.getTestMethod().getQualifiedName());
}

public List<String> getLogs() {
return logs;
}
}

public static class CustomTestNG extends TestNG {

@Override
Expand Down
Expand Up @@ -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 {

Expand Down
Expand Up @@ -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<CompiledCode> byteCodes = SimpleCompiler.compileSourceCode(sources);
List<Class<?>> classes = Lists.newArrayList();
Expand Down
17 changes: 15 additions & 2 deletions testng-core/src/test/java/testhelper/CompiledCode.java
Expand Up @@ -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());
}

Expand Down
@@ -1,4 +1,4 @@
package org.testng.jarfileutils;
package testhelper;

import java.io.File;
import java.io.IOException;
Expand Down
14 changes: 12 additions & 2 deletions testng-core/src/test/java/testhelper/SimpleCompiler.java
Expand Up @@ -25,8 +25,18 @@ public static List<CompiledCode> compileSourceCode(SourceCode... sources) throws
List<CompiledCode> 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;
Expand Down
34 changes: 30 additions & 4 deletions testng-core/src/test/java/testhelper/SourceCode.java
Expand Up @@ -3,26 +3,44 @@
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;
private final File location;

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() {
Expand All @@ -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;
}
Expand Down
38 changes: 38 additions & 0 deletions testng-core/src/test/java/testhelper/TestClassGenerator.java
@@ -0,0 +1,38 @@
package testhelper;

import java.io.File;
import java.io.IOException;
import java.util.List;

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 SourceCode[] generate(String packageName, List<String> classNames) {
return classNames.stream()
.map(className -> generateCode(packageName, className))
.toArray(SourceCode[]::new);
}

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);
}
}
}
36 changes: 36 additions & 0 deletions 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);
}
}

0 comments on commit b649672

Please sign in to comment.