Add diff assertion test
This commit is contained in:
@@ -137,6 +137,7 @@ public class Jtags {
|
||||
Usage: jtags [options] <sources...>
|
||||
Options:
|
||||
-o, -output <file> Write tags to specified <file>
|
||||
Use - for standard output
|
||||
-lib Treat sources as third-party libraries
|
||||
(alias for -no-non-public -no-anonymous)
|
||||
-no-anonymous Exclude anonymous classes
|
||||
|
||||
@@ -9,6 +9,7 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.nio.file.Path;
|
||||
import java.util.AbstractQueue;
|
||||
import xyz.naofal.jtags.Jtags.Options;
|
||||
|
||||
@@ -16,10 +17,16 @@ public record TagsWriter(Options options) {
|
||||
private static final int MAX_PATTERN_LENGTH = 96;
|
||||
|
||||
public boolean writeTagsFile(AbstractQueue<Tag> tags) {
|
||||
|
||||
try (var outputStream = new FileOutputStream(options().output.toFile());
|
||||
try (var outputStream =
|
||||
options().output.toString().equals("-")
|
||||
? System.out
|
||||
: new FileOutputStream(options().output.toFile());
|
||||
var writer = new OutputStreamWriter(outputStream); ) {
|
||||
|
||||
if (outputStream == System.out) {
|
||||
options().output = Path.of(".", "tags");
|
||||
}
|
||||
|
||||
writer.write(
|
||||
"""
|
||||
!_TAG_FILE_ENCODING\tutf-8
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import static notest.Test.Util;
|
||||
import static notest.Test.Util.*;
|
||||
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -9,26 +10,26 @@ class TestJtags {
|
||||
public static final String Jtags = "xyz.naofal.jtags.Jtags";
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.exit(Util.runTests(TestJtags.class) ? 0 : 1);
|
||||
runTests(TestJtags.class, args);
|
||||
}
|
||||
|
||||
@Test
|
||||
static void basicExample() throws IOException {
|
||||
Path file = Files.createTempFile("jtags", null);
|
||||
Util.runJava(
|
||||
new String[0],
|
||||
new String[] {"-Dlogger.level=CONFIG"},
|
||||
Jtags,
|
||||
"-o",
|
||||
file.toString(),
|
||||
"src/test/java/examples/BasicExample.java");
|
||||
assert Files.readString(file)
|
||||
.trim()
|
||||
.equals(
|
||||
"""
|
||||
ABC
|
||||
"""
|
||||
.trim())
|
||||
: "Unexpected Result:\n" + Files.readString(file);
|
||||
var tags =
|
||||
runJava(
|
||||
new String[] {"-Dlogger.level=CONFIG"},
|
||||
Jtags,
|
||||
"-o",
|
||||
"-",
|
||||
"src/test/java/examples/BasicExample.java")
|
||||
.inputReader();
|
||||
|
||||
Diff.diff(
|
||||
tags,
|
||||
new FileReader(
|
||||
Path.of("src", "test", "java", "snapshots", "BasicExample.basicExample.1")
|
||||
.toFile()))
|
||||
.assertEquals();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,10 @@
|
||||
*/
|
||||
package notest;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.lang.ProcessBuilder.Redirect;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
@@ -38,8 +40,11 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.PathMatcher;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.IntSummaryStatistics;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
@@ -56,43 +61,37 @@ public @interface Test {
|
||||
public static String javaBin = javaHome.resolve("bin", "java").toString();
|
||||
|
||||
/** Runs all tests in {@code testClass} */
|
||||
public static boolean runTests(Class<?> testClass) {
|
||||
public static void runTests(Class<?> testClass, String[] args) {
|
||||
System.exit(doRunTests(testClass) ? 0 : 1);
|
||||
}
|
||||
|
||||
/** Runs all tests in {@code testClass} */
|
||||
public static boolean doRunTests(Class<?> testClass) {
|
||||
IntSummaryStatistics stats =
|
||||
Arrays.stream(testClass.getDeclaredMethods())
|
||||
.filter(it -> it.isAnnotationPresent(Test.class))
|
||||
.map(
|
||||
it -> {
|
||||
try {
|
||||
System.err.println(
|
||||
"╭" + center(" Test " + it.getName() + " ", 48, '─') + "╮");
|
||||
System.err.println(boxTop("Test " + it.getName(), 50));
|
||||
it.setAccessible(true);
|
||||
it.invoke(null);
|
||||
System.err.println(
|
||||
"╰" + center(" " + it.getName() + ": SUCCESS ", 48, '─') + "╯");
|
||||
System.err.println(boxBottom(it.getName() + ": SUCCESS", 50));
|
||||
System.err.println();
|
||||
return 1;
|
||||
} catch (IllegalAccessException ex) {
|
||||
System.err.println(
|
||||
"╰"
|
||||
+ center(" ERROR: could not run " + it.getName() + " ", 48, '─')
|
||||
+ "╯");
|
||||
System.err.println(boxBottom("ERROR: could not run " + it.getName(), 50));
|
||||
System.err.println(ex.toString());
|
||||
System.err.println();
|
||||
return 0;
|
||||
} catch (InvocationTargetException ex) {
|
||||
System.err.println(
|
||||
"├"
|
||||
+ center(
|
||||
" " + ex.getTargetException().getClass().getSimpleName() + " ",
|
||||
48,
|
||||
'─')
|
||||
+ "┤");
|
||||
Optional.ofNullable(ex.getTargetException().getMessage())
|
||||
.orElse("")
|
||||
.lines()
|
||||
.forEach(line -> System.err.println("│ " + line));
|
||||
boxMiddle(ex.getTargetException().getClass().getSimpleName(), 50));
|
||||
System.err.println(
|
||||
"╰" + center(" " + it.getName() + ": FAIL ", 48, '─') + "╯");
|
||||
boxLeft(
|
||||
Optional.ofNullable(ex.getTargetException().getMessage())
|
||||
.orElse("")));
|
||||
System.err.println(boxBottom(it.getName() + ": FAIL", 50));
|
||||
System.err.println();
|
||||
return 0;
|
||||
}
|
||||
@@ -111,10 +110,10 @@ public @interface Test {
|
||||
*
|
||||
* @param mainClass The fully qualified class name, e.g. {@code com.example.MyClass$MySubClass}
|
||||
* @param args Arguments to pass to the main method
|
||||
* @return Exit status code
|
||||
* @return A {@link Process} representing the started process
|
||||
*/
|
||||
public static int runJava(String mainClass, String... args) {
|
||||
return runJava(new String[0], new String[0], mainClass, args);
|
||||
public static Process runJava(String mainClass, String... args) {
|
||||
return runJava(new String[0], mainClass, args);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,46 +123,25 @@ public @interface Test {
|
||||
* @param javaArguments Arguments to pass to the java binary
|
||||
* @param mainClass The fully qualified class name, e.g. {@code com.example.MyClass$MySubClass}
|
||||
* @param args Arguments to pass to the main method
|
||||
* @return Exit status code
|
||||
* @return A {@link Process} representing the started process
|
||||
*/
|
||||
public static int runJava(
|
||||
String[] additionalClassPaths, String[] javaArguments, String mainClass, String... args) {
|
||||
String pathSeparator = System.getProperty("path.separator");
|
||||
|
||||
String classPaths =
|
||||
String.join(
|
||||
pathSeparator,
|
||||
Stream.concat(Stream.of(javaClassPath), Arrays.stream(additionalClassPaths))
|
||||
.toArray(String[]::new));
|
||||
|
||||
public static Process runJava(String[] javaArguments, String mainClass, String... args) {
|
||||
Stream<String> commandLineStream =
|
||||
Stream.of(
|
||||
Stream.of(javaBin.toString(), "-cp", classPaths),
|
||||
Stream.of(javaBin),
|
||||
Arrays.stream(javaArguments),
|
||||
Stream.of(mainClass),
|
||||
Arrays.stream(args))
|
||||
.flatMap(it -> it);
|
||||
|
||||
return command(commandLineStream.toArray(String[]::new));
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a shell command. May throw an {@code IOException}
|
||||
*
|
||||
* @return Exit status code
|
||||
* @throws Exception
|
||||
*/
|
||||
public static int command(String... command) {
|
||||
ProcessBuilder pb =
|
||||
new ProcessBuilder(command)
|
||||
.redirectInput(Redirect.INHERIT)
|
||||
.redirectOutput(Redirect.INHERIT)
|
||||
.redirectError(Redirect.INHERIT);
|
||||
Process process;
|
||||
new ProcessBuilder(commandLineStream.toArray(String[]::new))
|
||||
.redirectInput(Redirect.PIPE)
|
||||
.redirectOutput(Redirect.PIPE)
|
||||
.redirectError(Redirect.PIPE);
|
||||
|
||||
try {
|
||||
process = pb.start();
|
||||
process.waitFor();
|
||||
return process.exitValue();
|
||||
return pb.start();
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
@@ -192,6 +170,82 @@ public @interface Test {
|
||||
}
|
||||
}
|
||||
|
||||
public static record Diff(List<String> diff) {
|
||||
|
||||
public static Diff diff(Reader readerA, Reader readerB) throws IOException {
|
||||
List<String> diff = new ArrayList<>();
|
||||
|
||||
BufferedReader
|
||||
bufReaderA = readerA instanceof BufferedReader ra ? ra : new BufferedReader(readerA),
|
||||
bufReaderB = readerB instanceof BufferedReader rb ? rb : new BufferedReader(readerB);
|
||||
|
||||
String a = bufReaderA.readLine(), b = bufReaderB.readLine();
|
||||
boolean different = false;
|
||||
while (a != null || b != null) {
|
||||
if (a == null || b == null || (a != null && !a.equals(b))) {
|
||||
different = true;
|
||||
if (a != null) {
|
||||
diff.add("-" + a);
|
||||
}
|
||||
if (b != null) {
|
||||
diff.add("+" + b);
|
||||
}
|
||||
} else {
|
||||
diff.add(" " + a);
|
||||
}
|
||||
|
||||
a = bufReaderA.readLine();
|
||||
b = bufReaderB.readLine();
|
||||
}
|
||||
|
||||
if (different) {
|
||||
diff.sort(
|
||||
new Comparator<String>() {
|
||||
public int compare(String s1, String s2) {
|
||||
char a = s1.charAt(0), b = s2.charAt(0);
|
||||
if (a == ' ' || b == ' ') return 0;
|
||||
return b - a;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
diff = List.of();
|
||||
}
|
||||
|
||||
return new Diff(diff);
|
||||
}
|
||||
|
||||
public void assertEquals() {
|
||||
if (diff.isEmpty()) return;
|
||||
|
||||
throw new AssertionError(
|
||||
"Result differs from expected. Diff:\n"
|
||||
+ String.join(
|
||||
"\n",
|
||||
diff.stream()
|
||||
.map(
|
||||
it ->
|
||||
switch (it.charAt(0)) {
|
||||
case ' ' -> it;
|
||||
case '-' ->
|
||||
"\u001b[38;5;1m%s\u001b[0m"
|
||||
.formatted(it)
|
||||
.replace(' ', '·')
|
||||
.replace('\t', '⇥')
|
||||
.replaceAll("·+", "\u001b[38;5;8m$0\u001b[38;5;1m")
|
||||
.replaceAll("⇥+", "\u001b[38;5;8m$0\u001b[38;5;1m");
|
||||
case '+' ->
|
||||
"\u001b[38;5;2m%s\u001b[0m"
|
||||
.formatted(it)
|
||||
.replace(' ', '·')
|
||||
.replace('\t', '⇥')
|
||||
.replaceAll("·+", "\u001b[38;5;8m$0\u001b[38;5;2m")
|
||||
.replaceAll("⇥+", "\u001b[38;5;8m$0\u001b[38;5;2m");
|
||||
default -> throw new IllegalArgumentException();
|
||||
})
|
||||
.toList()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pads string to center.
|
||||
*
|
||||
@@ -210,5 +264,22 @@ public @interface Test {
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String boxTop(String string, int width) {
|
||||
return "╭" + center(" " + string + " ", width - 2, '─') + "╮";
|
||||
}
|
||||
|
||||
private static String boxMiddle(String string, int width) {
|
||||
return "├" + center(" " + string + " ", width - 2, '─') + "┤";
|
||||
}
|
||||
|
||||
private static String boxBottom(String string, int width) {
|
||||
return "╰" + center(" " + string + " ", width - 2, '─') + "╯";
|
||||
}
|
||||
|
||||
private static String boxLeft(String string) {
|
||||
return string;
|
||||
// return String.join("\n", string.lines().map(it -> "│ " + it).toList());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
9
src/test/java/snapshots/BasicExample.basicExample.1
Normal file
9
src/test/java/snapshots/BasicExample.basicExample.1
Normal file
@@ -0,0 +1,9 @@
|
||||
!_TAG_FILE_ENCODING utf-8
|
||||
!_TAG_FILE_SORTED 2 /0=unsorted, 1=sorted, 2=foldcase/
|
||||
BasicExample src/test/java/examples/BasicExample.java /^public class BasicExample {$/;" Cls package:examples
|
||||
InnerClass src/test/java/examples/BasicExample.java /^ class InnerClass {$/;" Cls package:examples class:BasicExample
|
||||
method1 src/test/java/examples/BasicExample.java /^ private boolean method1() {$/;" mthd class:BasicExample
|
||||
ABC lorem ipsum dolor
|
||||
method2 src/test/java/examples/BasicExample.java /^ public static void method2($/;" mthd file: class:BasicExample
|
||||
method3 src/test/java/examples/BasicExample.java /^ void method3() {}$/;" mthd class:InnerClass
|
||||
method4 src/test/java/examples/BasicExample.java /^ private void method4() {}$/;" mthd class:InnerClass
|
||||
Reference in New Issue
Block a user