Skip to content

Commit 5bac009

Browse files
Introducing a test framework for Java APIView processor (#8115)
Introducing a test framework for Java APIView processor to compare output for published libraries against their known-good representations, to help avoid regrettable regressions. There are no test outputs in place yet - they will be added after the token refactoring is done.
1 parent 93fbd66 commit 5bac009

2 files changed

Lines changed: 179 additions & 22 deletions

File tree

src/java/apiview-java-processor/src/main/java/com/azure/tools/apiview/processor/Main.java

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.nio.file.Files;
2323
import java.nio.file.Path;
2424
import java.util.ArrayList;
25+
import java.util.Arrays;
2526
import java.util.Enumeration;
2627
import java.util.List;
2728
import java.util.jar.JarEntry;
@@ -33,7 +34,7 @@
3334
public class Main {
3435
// expected argument order:
3536
// [inputFiles] <outputDirectory>
36-
public static void main(String[] args) throws IOException {
37+
public static void main(String[] args) {
3738
if (args.length != 2) {
3839
System.out.println("Expected argument order: [comma-separated sources jarFiles] <outputFile>, e.g. /path/to/jarfile.jar ./temp/");
3940
System.exit(-1);
@@ -43,28 +44,35 @@ public static void main(String[] args) throws IOException {
4344
final String[] jarFilesArray = jarFiles.split(",");
4445

4546
final File outputDir = new File(args[1]);
46-
if (!outputDir.exists()) {
47-
if (!outputDir.mkdirs()) {
48-
System.out.printf("Failed to create output directory %s%n", outputDir);
49-
}
50-
}
5147

5248
System.out.println("Running with following configuration:");
5349
System.out.printf(" Output directory: '%s'%n", outputDir);
5450

55-
for (final String jarFile : jarFilesArray) {
56-
System.out.printf(" Processing input .jar file: '%s'%n", jarFile);
51+
Arrays.stream(jarFilesArray).forEach(jarFile -> run(new File(jarFile), outputDir));
52+
}
5753

58-
final File file = new File(jarFile);
59-
if (!file.exists()) {
60-
System.out.printf("Cannot find file '%s'%n", file);
54+
/**
55+
* Runs APIView parser and returns the output file path.
56+
*/
57+
public static File run(File jarFile, File outputDir) {
58+
System.out.printf(" Processing input .jar file: '%s'%n", jarFile);
59+
60+
if (!jarFile.exists()) {
61+
System.out.printf("Cannot find file '%s'%n", jarFile);
62+
System.exit(-1);
63+
}
64+
65+
if (!outputDir.exists()) {
66+
if (!outputDir.mkdirs()) {
67+
System.out.printf("Failed to create output directory %s%n", outputDir);
6168
System.exit(-1);
6269
}
63-
64-
final String jsonFileName = file.getName().substring(0, file.getName().length() - 4) + ".json";
65-
final File outputFile = new File(outputDir, jsonFileName);
66-
processFile(file, outputFile);
6770
}
71+
72+
final String jsonFileName = jarFile.getName().substring(0, jarFile.getName().length() - 4) + ".json";
73+
final File outputFile = new File(outputDir, jsonFileName);
74+
processFile(jarFile, outputFile);
75+
return outputFile;
6876
}
6977

7078
private static ReviewProperties getReviewProperties(File inputFile) {
@@ -104,7 +112,7 @@ private static ReviewProperties getReviewProperties(File inputFile) {
104112
return reviewProperties;
105113
}
106114

107-
private static void processFile(final File inputFile, final File outputFile) throws IOException {
115+
private static void processFile(final File inputFile, final File outputFile) {
108116
final APIListing apiListing = new APIListing();
109117

110118
// empty tokens list that we will fill as we process each class file
@@ -126,13 +134,22 @@ private static void processFile(final File inputFile, final File outputFile) thr
126134
"that was submitted to APIView was named " + inputFile.getName()));
127135
}
128136

129-
// Write out to the filesystem
130-
try (JsonWriter jsonWriter = JsonProviders.createWriter(Files.newBufferedWriter(outputFile.toPath()))) {
131-
apiListing.toJson(jsonWriter);
137+
try {
138+
// Write out to the filesystem, make the file if it doesn't exist
139+
if (!outputFile.exists()) {
140+
if (!outputFile.createNewFile()) {
141+
System.out.printf("Failed to create output file %s%n", outputFile);
142+
}
143+
}
144+
try (JsonWriter jsonWriter = JsonProviders.createWriter(Files.newBufferedWriter(outputFile.toPath()))) {
145+
apiListing.toJson(jsonWriter);
146+
}
147+
} catch (IOException e) {
148+
e.printStackTrace();
132149
}
133150
}
134151

135-
private static void processJavaSourcesJar(File inputFile, APIListing apiListing) throws IOException {
152+
private static void processJavaSourcesJar(File inputFile, APIListing apiListing) {
136153
final ReviewProperties reviewProperties = getReviewProperties(inputFile);
137154

138155
final String groupId = reviewProperties.getMavenPom().getGroupId();
@@ -260,8 +277,6 @@ private static void processXmlFile(File inputFile, APIListing apiListing) {
260277
} catch (IOException e) {
261278
e.printStackTrace();
262279
}
263-
264-
265280
}
266281

267282
private static class ReviewProperties {
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package com.azure.tools.apiview;
2+
3+
import com.azure.tools.apiview.processor.Main;
4+
import org.junit.jupiter.api.*;
5+
import org.junit.jupiter.params.*;
6+
import org.junit.jupiter.params.provider.*;
7+
8+
import java.net.URL;
9+
import java.nio.file.*;
10+
import java.io.*;
11+
import java.util.stream.Stream;
12+
13+
import static org.junit.jupiter.api.Assertions.*;
14+
15+
public class TestExpectedOutputs {
16+
// This is a flag to add a '-DELETE' suffix to the generated output file if the expected output file is missing.
17+
// By default we do, but if we want to regenerate a bunch of expected outputs, we can see this to false to save
18+
// having to rename.
19+
private static final boolean ADD_DELETE_PREFIX_FOR_MISSING_EXPECTED_OUTPUTS = true;
20+
21+
private static final String root = "src/test/resources/";
22+
private static final File tempDir = new File(root + "temp");
23+
24+
private static Stream<String> provideFileNames() {
25+
// This is a stream of local files or URLs to download the file from (or a combination of both). For each value,
26+
// if it starts with http, it'll try downloading the file into a temp location. If it sees no http, it'll look
27+
// for the file in the inputs directory. To prevent clogging up the repository with source jars, it is
28+
// preferable to download the source jars from known-good places.
29+
return Stream.of(
30+
""
31+
// "https://repo1.maven.org/maven2/com/azure/azure-core/1.48.0/azure-core-1.48.0-sources.jar",
32+
// "https://repo1.maven.org/maven2/com/azure/azure-communication-chat/1.5.0/azure-communication-chat-1.5.0-sources.jar",
33+
// "https://repo1.maven.org/maven2/com/azure/azure-security-keyvault-keys/4.8.2/azure-security-keyvault-keys-4.8.2-sources.jar",
34+
// "https://repo1.maven.org/maven2/com/azure/azure-data-appconfiguration/1.6.0/azure-data-appconfiguration-1.6.0-sources.jar",
35+
// "https://repo1.maven.org/maven2/com/azure/azure-messaging-eventhubs/5.18.2/azure-messaging-eventhubs-5.18.2-sources.jar",
36+
// "https://repo1.maven.org/maven2/com/azure/azure-messaging-servicebus/7.15.2/azure-messaging-servicebus-7.15.2-sources.jar",
37+
// "https://repo1.maven.org/maven2/com/azure/azure-identity/1.12.0/azure-identity-1.12.0-sources.jar",
38+
// "https://repo1.maven.org/maven2/com/azure/azure-storage-blob/12.25.3/azure-storage-blob-12.25.3-sources.jar",
39+
// "https://repo1.maven.org/maven2/com/azure/azure-cosmos/4.57.0/azure-cosmos-4.57.0-sources.jar"
40+
);
41+
}
42+
43+
@BeforeAll
44+
public static void setup() {
45+
tempDir.mkdirs();
46+
}
47+
48+
@ParameterizedTest
49+
@MethodSource("provideFileNames")
50+
public void testGeneratedJson(String filenameOrUrl) {
51+
if (filenameOrUrl.isEmpty()) {
52+
return;
53+
}
54+
55+
String filename;
56+
Path inputFile;
57+
58+
if (filenameOrUrl.startsWith("http")) {
59+
// download the file if it isn't local...
60+
filename = filenameOrUrl.substring(filenameOrUrl.lastIndexOf('/') + 1);
61+
62+
// download the file and save it to the temp directory
63+
String destinationFile = tempDir + "/" + filename;
64+
downloadFile(filenameOrUrl, destinationFile);
65+
66+
// strip off the file extension
67+
filename = filename.substring(0, filename.lastIndexOf('.'));
68+
inputFile = Paths.get(destinationFile);
69+
} else {
70+
// the file is local, so we can go straight to it
71+
filename = filenameOrUrl;
72+
inputFile = Paths.get(root + "inputs/" + filename + ".jar");
73+
}
74+
75+
final Path expectedOutputFile = Paths.get(root + "expected-outputs/" + filename + ".json");
76+
77+
// Run the processor, receiving the name of the file that was generated
78+
final Path actualOutputFile = Main.run(inputFile.toFile(), tempDir).toPath();
79+
80+
if (expectedOutputFile.toFile().exists()) {
81+
try {
82+
// Compare the temporary file to the expected-outputs file
83+
assertArrayEquals(Files.readAllBytes(expectedOutputFile), Files.readAllBytes(actualOutputFile));
84+
} catch (IOException e) {
85+
fail("Failed to compare the actual output file with the expected output file.", e);
86+
}
87+
} else {
88+
// the test was never going to pass as we don't have an expected file to compare against.
89+
try {
90+
final String outputFile = root + "expected-outputs/" + filename + (ADD_DELETE_PREFIX_FOR_MISSING_EXPECTED_OUTPUTS ? "-DELETE" : "") + ".json";
91+
final Path suggestedOutputFile = Paths.get(outputFile);
92+
93+
if (!suggestedOutputFile.toFile().getParentFile().exists()) {
94+
suggestedOutputFile.toFile().getParentFile().mkdirs();
95+
}
96+
Files.move(actualOutputFile, suggestedOutputFile, StandardCopyOption.REPLACE_EXISTING);
97+
} catch (IOException e) {
98+
// unable to move file
99+
e.printStackTrace();
100+
}
101+
if (ADD_DELETE_PREFIX_FOR_MISSING_EXPECTED_OUTPUTS) {
102+
fail("Could not find expected output file for test. The output from the sources.jar was added into the " +
103+
"/expected-outputs directory, but with a '-DELETE' filename suffix. Please review and rename if it " +
104+
"should be included in the test suite.");
105+
} else {
106+
fail("Could not find expected output file for test. The output from the sources.jar was added into the " +
107+
"/expected-outputs directory without any -DELETE suffix. It will be used next time the test is run!");
108+
109+
}
110+
}
111+
}
112+
113+
@AfterAll
114+
public static void tearDown() throws IOException {
115+
// Delete the temporary file
116+
if (tempDir.exists()) {
117+
// delete everything in tempDir and then delete the directory
118+
Files.walk(tempDir.toPath())
119+
.map(Path::toFile)
120+
.forEach(File::delete);
121+
122+
try {
123+
Files.delete(tempDir.toPath());
124+
} catch (NoSuchFileException e) {
125+
// ignore
126+
}
127+
}
128+
}
129+
130+
private static void downloadFile(String urlStr, String destinationFile) {
131+
File file = new File(destinationFile);
132+
if (file.exists()) {
133+
return;
134+
}
135+
try (InputStream in = new URL(urlStr).openStream()) {
136+
Files.copy(in, file.toPath(), StandardCopyOption.REPLACE_EXISTING);
137+
} catch (IOException e) {
138+
fail("Failed to download and / or copy the given URL to the given destination { url: "
139+
+ urlStr + ", destination: " + destinationFile + " }");
140+
}
141+
}
142+
}

0 commit comments

Comments
 (0)