Fix tests failing on Windows

Before this commit, tests in this change set
were failing on MS Windows due to the file
comparison method which was based on file
content hash comparison. With this method,
differences between OS line endings
(LF vs CRLF) in generated json files produce
different hashes.

This commit uses a logical json comparison
based on the jsonassert library.
This commit is contained in:
Mahmoud Ben Hassine
2021-02-04 16:01:22 +01:00
parent ee282ab97f
commit 2cc807de62
5 changed files with 181 additions and 101 deletions

View File

@@ -105,6 +105,7 @@
<com.ibm.jbatch-tck-spi.version>1.0</com.ibm.jbatch-tck-spi.version>
<javax.inject.version>1</javax.inject.version>
<jettison.version>1.2</jettison.version>
<jsonassert.version>1.5.0</jsonassert.version>
<!-- samples dependencies -->
<hibernate-entitymanager.version>5.4.24.Final</hibernate-entitymanager.version>

View File

@@ -211,6 +211,12 @@
<version>${xmlunit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.skyscreamer</groupId>
<artifactId>jsonassert</artifactId>
<version>${jsonassert.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2018 the original author or authors.
* Copyright 2018-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -44,4 +44,9 @@ public class GsonJsonFileItemWriterFunctionalTests extends JsonFileItemWriterFun
return "expected-trades-gson-pretty-print.json";
}
@Override
protected String getMarshallerName() {
return "gson";
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2018 the original author or authors.
* Copyright 2018-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,4 +45,9 @@ public class JacksonJsonFileItemWriterFunctionalTests extends JsonFileItemWriter
return "expected-trades-jackson-pretty-print.json";
}
@Override
protected String getMarshallerName() {
return "jackson";
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2018 the original author or authors.
* Copyright 2018-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,9 @@ package org.springframework.batch.item.json;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -29,6 +31,7 @@ import java.util.List;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.UnexpectedInputException;
@@ -54,9 +57,6 @@ public abstract class JsonFileItemWriterFunctionalTests {
private static final String EXPECTED_FILE_DIRECTORY = "src/test/resources/org/springframework/batch/item/json/";
private Resource resource;
private List<Trade> items;
private ExecutionContext executionContext;
private Trade trade1 = new Trade("123", 5, new BigDecimal("10.5"), "foo");
private Trade trade2 = new Trade("456", 10, new BigDecimal("20.5"), "bar");
private Trade trade3 = new Trade("789", 15, new BigDecimal("30.5"), "foobar");
@@ -65,217 +65,274 @@ public abstract class JsonFileItemWriterFunctionalTests {
protected abstract JsonObjectMarshaller<Trade> getJsonObjectMarshaller();
protected abstract JsonObjectMarshaller<Trade> getJsonObjectMarshallerWithPrettyPrint();
protected abstract String getExpectedPrettyPrintedFile();
private JsonFileItemWriter<Trade> writer;
@Before
public void setUp() throws Exception {
Path outputFilePath = Paths.get("target", "trades.json");
Files.deleteIfExists(outputFilePath);
this.resource = new FileSystemResource(outputFilePath.toFile());
this.executionContext = new ExecutionContext();
this.items = Arrays.asList(this.trade1, this.trade2);
this.writer = new JsonFileItemWriterBuilder<Trade>()
.name("tradesItemWriter")
.resource(this.resource)
.jsonObjectMarshaller(getJsonObjectMarshaller())
.build();
}
protected abstract String getMarshallerName();
@Test
public void testJsonWriting() throws Exception {
//given
Path outputFilePath = Paths.get("target", "trades-" + getMarshallerName() + ".json");
FileSystemResource resource = new FileSystemResource(outputFilePath);
JsonFileItemWriter<Trade> writer = new JsonFileItemWriterBuilder<Trade>()
.name("tradesItemWriter")
.resource(resource)
.jsonObjectMarshaller(getJsonObjectMarshaller())
.build();
// when
this.writer.open(this.executionContext);
this.writer.write(this.items);
this.writer.close();
writer.open(new ExecutionContext());
writer.write(Arrays.asList(this.trade1, this.trade2));
writer.close();
// then
assertFileEquals(
new File(EXPECTED_FILE_DIRECTORY + "expected-trades.json"),
this.resource.getFile());
resource.getFile());
}
@Test
public void testJsonWritingWithMultipleWrite() throws Exception {
//given
Path outputFilePath = Paths.get("target", "testJsonWritingWithMultipleWrite-" + getMarshallerName() + ".json");
FileSystemResource resource = new FileSystemResource(outputFilePath);
JsonFileItemWriter<Trade> writer = new JsonFileItemWriterBuilder<Trade>()
.name("tradesItemWriter")
.resource(resource)
.jsonObjectMarshaller(getJsonObjectMarshaller())
.build();
// when
this.writer.open(this.executionContext);
this.writer.write(this.items);
this.writer.write(Arrays.asList(trade3, trade4));
this.writer.close();
writer.open(new ExecutionContext());
writer.write(Arrays.asList(this.trade1, this.trade2));
writer.write(Arrays.asList(this.trade3, this.trade4));
writer.close();
// then
assertFileEquals(
new File(EXPECTED_FILE_DIRECTORY + "expected-trades-with-multiple-writes.json"),
this.resource.getFile());
resource.getFile());
}
@Test
public void testJsonWritingWithPrettyPrinting() throws Exception {
// given
this.writer = new JsonFileItemWriterBuilder<Trade>()
Path outputFilePath = Paths.get("target", "testJsonWritingWithPrettyPrinting-" + getMarshallerName() + ".json");
FileSystemResource resource = new FileSystemResource(outputFilePath);
JsonFileItemWriter<Trade> writer = new JsonFileItemWriterBuilder<Trade>()
.name("tradesItemWriter")
.resource(this.resource)
.resource(resource)
.jsonObjectMarshaller(getJsonObjectMarshallerWithPrettyPrint())
.build();
// when
this.writer.open(this.executionContext);
this.writer.write(this.items);
this.writer.close();
writer.open(new ExecutionContext());
writer.write(Arrays.asList(this.trade1, this.trade2));
writer.close();
// when
assertFileEquals(
new File(EXPECTED_FILE_DIRECTORY + getExpectedPrettyPrintedFile()),
this.resource.getFile());
resource.getFile());
}
@Test
public void testJsonWritingWithEnclosingObject() throws Exception {
// given
this.writer.setHeaderCallback(writer -> writer.write("{\"trades\":["));
this.writer.setFooterCallback(writer -> writer.write(JsonFileItemWriter.DEFAULT_LINE_SEPARATOR + "]}"));
Path outputFilePath = Paths.get("target", "testJsonWritingWithEnclosingObject-" + getMarshallerName() + ".json");
FileSystemResource resource = new FileSystemResource(outputFilePath);
JsonFileItemWriter<Trade> writer = new JsonFileItemWriterBuilder<Trade>()
.name("tradesItemWriter")
.resource(resource)
.jsonObjectMarshaller(getJsonObjectMarshaller())
.headerCallback(headerWriter -> headerWriter.write("{\"trades\":["))
.footerCallback(footerWriter -> footerWriter.write(JsonFileItemWriter.DEFAULT_LINE_SEPARATOR + "]}"))
.build();
// when
this.writer.open(this.executionContext);
this.writer.write(this.items);
this.writer.close();
writer.open(new ExecutionContext());
writer.write(Arrays.asList(this.trade1, this.trade2));
writer.close();
// then
assertFileEquals(
new File(EXPECTED_FILE_DIRECTORY + "expected-trades-with-wrapper-object.json"),
this.resource.getFile());
resource.getFile());
}
@Test
public void testForcedWrite() throws Exception {
// given
this.writer.setForceSync(true);
Path outputFilePath = Paths.get("target", "testForcedWrite-" + getMarshallerName() + ".json");
FileSystemResource resource = new FileSystemResource(outputFilePath);
JsonFileItemWriter<Trade> writer = new JsonFileItemWriterBuilder<Trade>()
.name("tradesItemWriter")
.resource(resource)
.jsonObjectMarshaller(getJsonObjectMarshaller())
.forceSync(true)
.build();
// when
this.writer.open(this.executionContext);
this.writer.write(Collections.singletonList(this.trade1));
this.writer.close();
writer.open(new ExecutionContext());
writer.write(Collections.singletonList(this.trade1));
writer.close();
// then
assertFileEquals(
new File(EXPECTED_FILE_DIRECTORY + "expected-trades1.json"),
this.resource.getFile());
resource.getFile());
}
@Test
public void testWriteWithDelete() throws Exception {
// given
this.writer.setShouldDeleteIfExists(true);
ExecutionContext executionContext = new ExecutionContext();
Path outputFilePath = Paths.get("target", "testWriteWithDelete-" + getMarshallerName() + ".json");
FileSystemResource resource = new FileSystemResource(outputFilePath);
JsonFileItemWriter<Trade> writer = new JsonFileItemWriterBuilder<Trade>()
.name("tradesItemWriter")
.resource(resource)
.jsonObjectMarshaller(getJsonObjectMarshaller())
.shouldDeleteIfExists(true)
.build();
// when
this.writer.open(this.executionContext);
this.writer.write(Collections.singletonList(this.trade1));
this.writer.close();
this.writer.open(this.executionContext);
this.writer.write(Collections.singletonList(this.trade2));
this.writer.close();
writer.open(executionContext);
writer.write(Collections.singletonList(this.trade1));
writer.close();
writer.open(executionContext);
writer.write(Collections.singletonList(this.trade2));
writer.close();
// then
assertFileEquals(
new File(EXPECTED_FILE_DIRECTORY + "expected-trades2.json"),
this.resource.getFile());
resource.getFile());
}
@Test
public void testRestart() throws Exception {
this.writer.open(this.executionContext);
// given
ExecutionContext executionContext = new ExecutionContext();
Path outputFilePath = Paths.get("target", "testRestart-" + getMarshallerName() + ".json");
FileSystemResource resource = new FileSystemResource(outputFilePath);
JsonFileItemWriter<Trade> writer = new JsonFileItemWriterBuilder<Trade>()
.name("tradesItemWriter")
.resource(resource)
.jsonObjectMarshaller(getJsonObjectMarshaller())
.build();
// when
writer.open(executionContext);
// write some lines
this.writer.write(Collections.singletonList(this.trade1));
writer.write(Collections.singletonList(this.trade1));
// get restart data
this.writer.update(this.executionContext);
writer.update(executionContext);
// close template
this.writer.close();
writer.close();
// init with correct data
this.writer.open(this.executionContext);
writer.open(executionContext);
// write more lines
this.writer.write(Collections.singletonList(this.trade2));
writer.write(Collections.singletonList(this.trade2));
// get statistics
this.writer.update(this.executionContext);
writer.update(executionContext);
// close template
this.writer.close();
writer.close();
// verify what was written to the file
assertFileEquals(
new File(EXPECTED_FILE_DIRECTORY+ "expected-trades.json"),
this.resource.getFile());
resource.getFile());
// 2 lines were written to the file in total
assertEquals(2, this.executionContext.getLong("tradesItemWriter.written"));
assertEquals(2, executionContext.getLong("tradesItemWriter.written"));
}
@Test
public void testTransactionalRestart() throws Exception {
this.writer.open(this.executionContext);
// given
PlatformTransactionManager transactionManager = new ResourcelessTransactionManager();
ExecutionContext executionContext = new ExecutionContext();
Path outputFilePath = Paths.get("target", "testTransactionalRestart-" + getMarshallerName() + ".json");
FileSystemResource resource = new FileSystemResource(outputFilePath);
JsonFileItemWriter<Trade> writer = new JsonFileItemWriterBuilder<Trade>()
.name("tradesItemWriter")
.resource(resource)
.jsonObjectMarshaller(getJsonObjectMarshaller())
.build();
// when
writer.open(executionContext);
new TransactionTemplate(transactionManager).execute((TransactionCallback<Void>) status -> {
try {
// write some lines
this.writer.write(Collections.singletonList(this.trade1));
writer.write(Collections.singletonList(this.trade1));
}
catch (Exception e) {
throw new UnexpectedInputException("Could not write data", e);
}
// get restart data
this.writer.update(this.executionContext);
writer.update(executionContext);
return null;
});
// close template
this.writer.close();
writer.close();
// init with correct data
this.writer.open(this.executionContext);
writer.open(executionContext);
new TransactionTemplate(transactionManager).execute((TransactionCallback<Void>) status -> {
try {
// write more lines
this.writer.write(Collections.singletonList(this.trade2));
writer.write(Collections.singletonList(this.trade2));
}
catch (Exception e) {
throw new UnexpectedInputException("Could not write data", e);
}
// get restart data
this.writer.update(this.executionContext);
writer.update(executionContext);
return null;
});
// close template
this.writer.close();
writer.close();
// verify what was written to the file
assertFileEquals(
new File(EXPECTED_FILE_DIRECTORY+ "expected-trades.json"),
this.resource.getFile());
resource.getFile());
// 2 lines were written to the file in total
assertEquals(2, this.executionContext.getLong("tradesItemWriter.written"));
assertEquals(2, executionContext.getLong("tradesItemWriter.written"));
}
@Test
public void testItemMarshallingFailure() throws Exception {
this.writer.setJsonObjectMarshaller(item -> {
throw new IllegalArgumentException("Bad item");
});
this.writer.open(this.executionContext);
// given
ExecutionContext executionContext = new ExecutionContext();
Path outputFilePath = Paths.get("target", "testItemMarshallingFailure-" + getMarshallerName() + ".json");
FileSystemResource resource = new FileSystemResource(outputFilePath);
JsonFileItemWriter<Trade> writer = new JsonFileItemWriterBuilder<Trade>()
.name("tradesItemWriter")
.resource(resource)
.jsonObjectMarshaller(item -> { throw new IllegalArgumentException("Bad item"); })
.build();
// when
writer.open(executionContext);
try {
this.writer.write(Collections.singletonList(this.trade1));
writer.write(Collections.singletonList(this.trade1));
fail();
}
catch (IllegalArgumentException iae) {
assertEquals("Bad item", iae.getMessage());
}
finally {
this.writer.close();
writer.close();
}
assertFileEquals(
new File(EXPECTED_FILE_DIRECTORY + "empty-trades.json"),
this.resource.getFile());
resource.getFile());
}
@Test
@@ -283,29 +340,35 @@ public abstract class JsonFileItemWriterFunctionalTests {
* If append=true a new output file should still be created on the first run (not restart).
*/
public void testAppendToNotYetExistingFile() throws Exception {
Resource toBeCreated = new FileSystemResource("target/FlatFileItemWriterTests.out");
// given
ExecutionContext executionContext = new ExecutionContext();
Path outputFilePath = Paths.get("target", "testAppendToNotYetExistingFile-" + getMarshallerName() + ".json");
FileSystemResource resource = new FileSystemResource(outputFilePath);
Files.deleteIfExists(outputFilePath);
JsonFileItemWriter<Trade> writer = new JsonFileItemWriterBuilder<Trade>()
.name("tradesItemWriter")
.resource(new FileSystemResource(outputFilePath))
.jsonObjectMarshaller(getJsonObjectMarshaller())
.append(true)
.build();
File outputFile = toBeCreated.getFile(); //enable easy content reading and auto-delete the file
assertFalse("output file does not exist yet", toBeCreated.exists());
this.writer.setResource(toBeCreated);
this.writer.setAppendAllowed(true);
this.writer.afterPropertiesSet();
this.writer.open(this.executionContext);
assertTrue("output file was created", toBeCreated.exists());
this.writer.write(Collections.singletonList(this.trade1));
this.writer.close();
// when
writer.open(executionContext);
writer.write(Collections.singletonList(this.trade1));
writer.close();
// then
assertFileEquals(
new File(EXPECTED_FILE_DIRECTORY + "expected-trades1.json"),
outputFile);
outputFile.delete();
resource.getFile());
}
private void assertFileEquals(File expected, File actual) throws Exception {
String expectedHash = DigestUtils.md5DigestAsHex(new FileInputStream(expected));
String actualHash = DigestUtils.md5DigestAsHex(new FileInputStream(actual));
Assert.assertEquals(expectedHash, actualHash);
JSONAssert.assertEquals(getContent(expected), getContent(actual), false);
}
private String getContent(File file) throws IOException {
return new String(Files.readAllBytes(file.toPath()), Charset.defaultCharset());
}
}