Skip to content

Commit

Permalink
issues/145: JSON serialization: Render numerical values as numbers (n…
Browse files Browse the repository at this point in the history
…ot strings)
  • Loading branch information
fmck3516 committed Mar 29, 2024
1 parent d4a3627 commit ef168c5
Show file tree
Hide file tree
Showing 12 changed files with 137 additions and 113 deletions.
27 changes: 14 additions & 13 deletions skippy-core/src/main/java/io/skippy/core/AnalyzedTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@
*/
final class AnalyzedTest implements Comparable<AnalyzedTest> {

private final String testClassId;
private final int testClassId;
private final TestResult result;
private final List<String> coveredClassesIds;
private final List<Integer> coveredClassesIds;
private final Optional<String> executionId;

/**
Expand All @@ -54,7 +54,7 @@ final class AnalyzedTest implements Comparable<AnalyzedTest> {
* @param coveredClassesIds the ids of the covered classes in the {@link ClassFileContainer}
* @param executionId a unique identifier for the test's JaCoCo execution data if capture of execution data is enabled
*/
AnalyzedTest(String testClassId, TestResult result, List<String> coveredClassesIds, Optional<String> executionId) {
AnalyzedTest(int testClassId, TestResult result, List<Integer> coveredClassesIds, Optional<String> executionId) {
this.testClassId = testClassId;
this.result = result;
this.coveredClassesIds = coveredClassesIds;
Expand All @@ -66,7 +66,7 @@ final class AnalyzedTest implements Comparable<AnalyzedTest> {
*
* @return the id of the test class in the {@link ClassFileContainer}
*/
String getTestClassId() {
int getTestClassId() {
return testClassId;
}

Expand All @@ -84,7 +84,7 @@ TestResult getResult() {
*
* @return the ids of the covered classes in the {@link ClassFileContainer}
*/
List<String> getCoveredClassesIds() {
List<Integer> getCoveredClassesIds() {
return coveredClassesIds;
}

Expand Down Expand Up @@ -112,16 +112,16 @@ static List<AnalyzedTest> parseList(Tokenizer tokenizer) {

static AnalyzedTest parse(Tokenizer tokenizer) {
tokenizer.skip('{');
String clazz = null;
List<String> coveredClasses = null;
Integer clazz = null;
List<Integer> coveredClasses = null;
TestResult testResult = null;
Optional<String> executionId = Optional.empty();
while (true) {
var key = tokenizer.next();
tokenizer.skip(':');
switch (key) {
case "class":
clazz = tokenizer.next();
clazz = Integer.valueOf(tokenizer.next());
break;
case "coveredClasses":
coveredClasses = parseCoveredClasses(tokenizer);
Expand All @@ -142,12 +142,12 @@ static AnalyzedTest parse(Tokenizer tokenizer) {
return new AnalyzedTest(clazz, testResult, coveredClasses, executionId);
}

static List<String> parseCoveredClasses(Tokenizer tokenizer) {
var coveredClasses = new ArrayList<String>();
static List<Integer> parseCoveredClasses(Tokenizer tokenizer) {
var coveredClasses = new ArrayList<Integer>();
tokenizer.skip('[');
while ( ! tokenizer.peek(']')) {
tokenizer.skipIfNext(',');
coveredClasses.add(tokenizer.next());
coveredClasses.add(Integer.valueOf(tokenizer.next()));
}
tokenizer.skip(']');
return coveredClasses;
Expand All @@ -156,14 +156,15 @@ static List<String> parseCoveredClasses(Tokenizer tokenizer) {
String toJson() {
var result = new StringBuilder();
result.append("\t\t{" + lineSeparator());
result.append("\t\t\t\"class\": \"%s\"".formatted(getTestClassId()));
result.append("\t\t\t\"class\": %s".formatted(getTestClassId()));
result.append(",%s".formatted(lineSeparator()));
result.append("\t\t\t\"result\": \"%s\"".formatted(getResult()));
result.append(",%s".formatted(lineSeparator()));
result.append("\t\t\t\"coveredClasses\": [%s]".formatted(getCoveredClassesIds().stream()
.map(Integer::valueOf)
.sorted()
.map(id -> "\"%s\"".formatted(id)).collect(joining(","))));
.map(val -> Integer.toString(val))
.collect(joining(","))));
if (executionId.isPresent()) {
result.append(",%s".formatted(lineSeparator()));
result.append("\t\t\t\"executionId\": \"%s\"".formatted(executionId.get()));
Expand Down
20 changes: 10 additions & 10 deletions skippy-core/src/main/java/io/skippy/core/ClassFileContainer.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,12 @@
final class ClassFileContainer {
private final Set<ClassFile> classFiles = new TreeSet<>();
private final Map<String, List<ClassFile>> classFilesByClassName = new HashMap<>();
private final Map<ClassFile, String> idsByClassFile = new HashMap<>();
private final Map<String, List<String>> idsByClassName = new HashMap<>();
private final Map<ClassFile, Integer> idsByClassFile = new HashMap<>();
private final Map<String, List<Integer>> idsByClassName = new HashMap<>();

private final Map<String, ClassFile> classFilesById = new HashMap<>();
private final Map<Integer, ClassFile> classFilesById = new HashMap<>();

private ClassFileContainer(Map<String, ClassFile> classFilesById) {
private ClassFileContainer(Map<Integer, ClassFile> classFilesById) {
for (var entry : classFilesById.entrySet()) {
var id = entry.getKey();
var classFile = entry.getValue();
Expand All @@ -87,7 +87,7 @@ private ClassFileContainer(Map<String, ClassFile> classFilesById) {
static ClassFileContainer from(List<ClassFile> classFiles) {
var sorted = classFiles.stream().sorted().toList();
return new ClassFileContainer(IntStream.range(0, classFiles.size()).boxed()
.collect(toMap(i -> Integer.toString(i), i -> sorted.get(i))));
.collect(toMap(i -> i, i -> sorted.get(i))));
}

/**
Expand All @@ -103,7 +103,7 @@ static ClassFileContainer from(List<ClassFile> classFiles) {
* @param className a class name
* @return the ids of the classes that match the provided class name
*/
List<String> getIdsByClassName(String className) {
List<Integer> getIdsByClassName(String className) {
if (false == idsByClassName.containsKey(className)) {
return emptyList();
}
Expand All @@ -120,7 +120,7 @@ Set<ClassFile> getClassFiles() {
* @param classFile a {@link ClassFile}
* @return the id for the given {@link ClassFile}
*/
String getId(ClassFile classFile) {
int getId(ClassFile classFile) {
return idsByClassFile.get(classFile);
}

Expand All @@ -131,7 +131,7 @@ String getId(ClassFile classFile) {
* @param id an id
* @return the {@link ClassFile} with the given id
*/
ClassFile getById(String id) {
ClassFile getById(int id) {
return classFilesById.get(id);
}

Expand All @@ -154,9 +154,9 @@ String toJson() {
static ClassFileContainer parse(Tokenizer tokenizer) {
return Profiler.profile("ClassFileContainer#parse", () -> {
tokenizer.skip('{');
var classFiles = new HashMap<String, ClassFile>();
var classFiles = new HashMap<Integer, ClassFile>();
while (!tokenizer.peek('}')) {
var id = tokenizer.next();
var id = Integer.valueOf(tokenizer.next());
tokenizer.skip(':');
classFiles.put(id, ClassFile.parse(tokenizer));
tokenizer.skipIfNext(',');
Expand Down
4 changes: 2 additions & 2 deletions skippy-core/src/main/java/io/skippy/core/SkippyApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ private List<AnalyzedTest> getAnalyzedTests(
.toList();
}

private List<String> getCoveredClassesIds(List<String> coveredClasses, ClassFileContainer classFileContainer) {
var coveredClassIds = new LinkedList<String>();
private List<Integer> getCoveredClassesIds(List<String> coveredClasses, ClassFileContainer classFileContainer) {
var coveredClassIds = new LinkedList<Integer>();
for (String clazz : coveredClasses) {
coveredClassIds.addAll(classFileContainer.getIdsByClassName(clazz));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,8 @@ private SkippyRepository(SkippyConfiguration skippyConfiguration, Path projectDi
* Returns the {@link SkippyRepository} instance for Skippy's build plugins.
*
* @param skippyConfiguration the {@link SkippyConfiguration}
* @param projectDir the project directory
* @param projectDir the build directory
* @param projectDir the project directory (e.g., ~/repo)
* @param buildDirectory the project's build directory (e.g., ~/repo/build or ~/repo/target)
* @return the {@link SkippyRepository}
*/
public static SkippyRepository getInstance(SkippyConfiguration skippyConfiguration, Path projectDir, Path buildDirectory) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ private AnalyzedTest remap(AnalyzedTest analyzedTest, ClassFileContainer origina
analyzedTest.getExecutionId());
}

private String remap(String id, ClassFileContainer original, ClassFileContainer merged) {
private int remap(int id, ClassFileContainer original, ClassFileContainer merged) {
var classFile = original.getById(id);
return merged.getId(classFile);
}
Expand Down
22 changes: 22 additions & 0 deletions skippy-core/src/main/java/io/skippy/core/Tokenizer.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

import java.util.Arrays;

import static java.lang.Character.isDigit;

/**
* Home-grown JSON tokenization to avoid a transitive dependencies to Jackson (or some other JSON library).
*
Expand Down Expand Up @@ -56,6 +58,7 @@ String next() {
head++;
return "{";
}
// read string
if (peek('"')) {
int pointer = head + 1;
while (stream[pointer] != '"') {
Expand All @@ -65,6 +68,17 @@ String next() {
head = pointer + 1;
return result;
}

// read number
if (peekDigit()) {
int pointer = head;
while (pointer < stream.length && isDigit(stream[pointer])) {
pointer++;
}
var result = new String(Arrays.copyOfRange(stream, head, pointer));
head = pointer;
return result;
}
throw new IllegalStateException("Unable to determine next token in residual characters '%s'.".formatted(asString()));
}

Expand All @@ -74,6 +88,14 @@ boolean peek(char c) {
}
return stream[head] == c;
}

boolean peekDigit() {
if (head == tail) {
return false;
}
return isDigit(stream[head]);
}

void skipIfNext(char c) {
if (head == tail) {
return;
Expand Down
42 changes: 21 additions & 21 deletions skippy-core/src/test/java/io/skippy/core/AnalyzedTestTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ public class AnalyzedTestTest {

@Test
void testToJsonNoCoveredClasses() throws JSONException {
var analyzedTest = new AnalyzedTest("0", TestResult.PASSED, asList(), Optional.empty());
var analyzedTest = new AnalyzedTest(0, TestResult.PASSED, asList(), Optional.empty());

var expected = """
{
"class": "0",
"class": 0,
"result": "PASSED",
"coveredClasses": []
}
Expand All @@ -44,35 +44,35 @@ void testToJsonNoCoveredClasses() throws JSONException {

@Test
void testToJsonOneCoveredClass() throws JSONException {
var analyzedTest = new AnalyzedTest("0", TestResult.PASSED, asList("0"), Optional.empty());
var analyzedTest = new AnalyzedTest(0, TestResult.PASSED, asList(0), Optional.empty());
var expected = """
{
"class": "0",
"class": 0,
"result": "PASSED",
"coveredClasses": ["0"]
"coveredClasses": [0]
}
""";
JSONAssert.assertEquals(expected, analyzedTest.toJson(), JSONCompareMode.LENIENT);
}
@Test
void testToJsonTwoCoveredClasses() throws JSONException {
var analyzedTest = new AnalyzedTest("0", TestResult.PASSED, asList("0", "1"), Optional.empty());
var analyzedTest = new AnalyzedTest(0, TestResult.PASSED, asList(0, 1), Optional.empty());
var expected = """
{
"class": "0",
"class": 0,
"result": "PASSED",
"coveredClasses": ["0", "1"]
"coveredClasses": [0, 1]
}
""";
JSONAssert.assertEquals(expected, analyzedTest.toJson(), JSONCompareMode.LENIENT);
}

@Test
void testToJsonFailedTest() throws JSONException {
var analyzedTest = new AnalyzedTest("0", TestResult.FAILED, asList(), Optional.empty());
var analyzedTest = new AnalyzedTest(0, TestResult.FAILED, asList(), Optional.empty());
var expected = """
{
"class": "0",
"class": 0,
"result": "FAILED",
"coveredClasses": []
}
Expand All @@ -84,13 +84,13 @@ void testToJsonFailedTest() throws JSONException {
void testParseNoCoveredClasses() {
var analyzedTest = AnalyzedTest.parse(new Tokenizer("""
{
"class": "0",
"class": 0,
"result": "PASSED",
"coveredClasses": []
}
"""));

assertEquals("0", analyzedTest.getTestClassId());
assertEquals(0, analyzedTest.getTestClassId());
assertEquals(TestResult.PASSED, analyzedTest.getResult());
assertEquals(asList(), analyzedTest.getCoveredClassesIds());
}
Expand All @@ -99,31 +99,31 @@ void testParseNoCoveredClasses() {
void testParseOneCoveredClass() {
var analyzedTest = AnalyzedTest.parse(new Tokenizer("""
{
"class": "0",
"class": 0,
"result": "PASSED",
"coveredClasses": ["0"]
"coveredClasses": [0]
}
"""));
assertEquals(asList("0"), analyzedTest.getCoveredClassesIds());
assertEquals(asList(0), analyzedTest.getCoveredClassesIds());
}

@Test
void testParseTwoCoveredClasses() {
var analyzedTest = AnalyzedTest.parse(new Tokenizer("""
{
"class": "0",
"class": 0,
"result": "PASSED",
"coveredClasses": ["0", "1"]
"coveredClasses": [0, 1]
}
"""));
assertEquals(asList("0", "1"), analyzedTest.getCoveredClassesIds());
assertEquals(asList(0, 1), analyzedTest.getCoveredClassesIds());
}

@Test
void testParseFailedTest() {
var analyzedTest = AnalyzedTest.parse(new Tokenizer("""
{
"class": "0",
"class": 0,
"result": "FAILED",
"coveredClasses": []
}
Expand All @@ -135,7 +135,7 @@ void testParseFailedTest() {
void testParseWithoutExecutionId() {
var analyzedTest = AnalyzedTest.parse(new Tokenizer("""
{
"class": "0",
"class": 0,
"result": "FAILED",
"coveredClasses": []
}
Expand All @@ -147,7 +147,7 @@ void testParseWithoutExecutionId() {
void testParseWithExecutionId() {
var analyzedTest = AnalyzedTest.parse(new Tokenizer("""
{
"class": "0",
"class": 0,
"result": "FAILED",
"coveredClasses": [],
"executionId": "00000000000000000000000000000000"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,10 @@ void testParse() {
}
"""));
assertEquals(2, classFileContainer.getClassFiles().size());
assertEquals("com.example.LeftPadder", classFileContainer.getById("0").getClassName());
assertEquals("com.example.LeftPadderTest", classFileContainer.getById("1").getClassName());
assertEquals(asList("0"), classFileContainer.getIdsByClassName("com.example.LeftPadder"));
assertEquals(asList("1"), classFileContainer.getIdsByClassName("com.example.LeftPadderTest"));
assertEquals("com.example.LeftPadder", classFileContainer.getById(0).getClassName());
assertEquals("com.example.LeftPadderTest", classFileContainer.getById(1).getClassName());
assertEquals(asList(0), classFileContainer.getIdsByClassName("com.example.LeftPadder"));
assertEquals(asList(1), classFileContainer.getIdsByClassName("com.example.LeftPadderTest"));
}

@Test
Expand Down
Loading

0 comments on commit ef168c5

Please sign in to comment.