Add diff assertion test

This commit is contained in:
naofal.helal
2025-03-27 14:50:29 +03:00
parent 011e46ec0f
commit 30faa32792
5 changed files with 161 additions and 72 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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());
}
}
}

View 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