mainframe-migration-toolkit 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mainframe_migration_toolkit-0.2.0.dist-info/METADATA +16 -0
- mainframe_migration_toolkit-0.2.0.dist-info/RECORD +52 -0
- mainframe_migration_toolkit-0.2.0.dist-info/WHEEL +4 -0
- mainframe_migration_toolkit-0.2.0.dist-info/entry_points.txt +3 -0
- mainframe_toolkit/__init__.py +92 -0
- mainframe_toolkit/__main__.py +5 -0
- mainframe_toolkit/_workspace/.claude/skills/analyze-mainframe-similarity/SKILL.md +30 -0
- mainframe_toolkit/_workspace/.claude/skills/migrate-mainframe-job/SKILL.md +63 -0
- mainframe_toolkit/_workspace/.claude/skills/validate-golden-dataset/SKILL.md +12 -0
- mainframe_toolkit/_workspace/AGENTS.md +10 -0
- mainframe_toolkit/_workspace/CLAUDE.md +2 -0
- mainframe_toolkit/_workspace/validator-java/.mvn/wrapper/maven-wrapper.properties +3 -0
- mainframe_toolkit/_workspace/validator-java/mvnw +295 -0
- mainframe_toolkit/_workspace/validator-java/mvnw.cmd +189 -0
- mainframe_toolkit/_workspace/validator-java/pom.xml +116 -0
- mainframe_toolkit/_workspace/validator-java/src/main/java/io/mainframe/migration/validator/AvroValueFormatter.java +152 -0
- mainframe_toolkit/_workspace/validator-java/src/main/java/io/mainframe/migration/validator/CsvTabularReader.java +111 -0
- mainframe_toolkit/_workspace/validator-java/src/main/java/io/mainframe/migration/validator/DataFormat.java +62 -0
- mainframe_toolkit/_workspace/validator-java/src/main/java/io/mainframe/migration/validator/Difference.java +34 -0
- mainframe_toolkit/_workspace/validator-java/src/main/java/io/mainframe/migration/validator/InputFileSet.java +45 -0
- mainframe_toolkit/_workspace/validator-java/src/main/java/io/mainframe/migration/validator/InputOptions.java +23 -0
- mainframe_toolkit/_workspace/validator-java/src/main/java/io/mainframe/migration/validator/JsonReportWriter.java +40 -0
- mainframe_toolkit/_workspace/validator-java/src/main/java/io/mainframe/migration/validator/MultiFileTabularReader.java +80 -0
- mainframe_toolkit/_workspace/validator-java/src/main/java/io/mainframe/migration/validator/Normalization.java +16 -0
- mainframe_toolkit/_workspace/validator-java/src/main/java/io/mainframe/migration/validator/ParquetTabularReader.java +61 -0
- mainframe_toolkit/_workspace/validator-java/src/main/java/io/mainframe/migration/validator/TabularReader.java +13 -0
- mainframe_toolkit/_workspace/validator-java/src/main/java/io/mainframe/migration/validator/TabularReaderFactory.java +29 -0
- mainframe_toolkit/_workspace/validator-java/src/main/java/io/mainframe/migration/validator/ValidationOptions.java +40 -0
- mainframe_toolkit/_workspace/validator-java/src/main/java/io/mainframe/migration/validator/ValidationReport.java +44 -0
- mainframe_toolkit/_workspace/validator-java/src/main/java/io/mainframe/migration/validator/ValidationService.java +395 -0
- mainframe_toolkit/_workspace/validator-java/src/main/java/io/mainframe/migration/validator/ValidatorCli.java +190 -0
- mainframe_toolkit/_workspace/validator-java/src/main/java/io/mainframe/migration/validator/ValueNormalizer.java +27 -0
- mainframe_toolkit/_workspace/validator-java/src/test/java/io/mainframe/migration/validator/DirectoryValidationTest.java +70 -0
- mainframe_toolkit/_workspace/validator-java/src/test/java/io/mainframe/migration/validator/JsonReportWriterTest.java +43 -0
- mainframe_toolkit/_workspace/validator-java/src/test/java/io/mainframe/migration/validator/KeyedValidationTest.java +89 -0
- mainframe_toolkit/_workspace/validator-java/src/test/java/io/mainframe/migration/validator/ParquetValidationTest.java +146 -0
- mainframe_toolkit/_workspace/validator-java/src/test/java/io/mainframe/migration/validator/ValidationServiceTest.java +137 -0
- mainframe_toolkit/_workspace/validator-java/src/test/java/io/mainframe/migration/validator/ValidatorCliTest.java +102 -0
- mainframe_toolkit/cli.py +361 -0
- mainframe_toolkit/cobol.py +106 -0
- mainframe_toolkit/copybook.py +558 -0
- mainframe_toolkit/errors.py +15 -0
- mainframe_toolkit/external.py +48 -0
- mainframe_toolkit/io.py +202 -0
- mainframe_toolkit/jcl.py +126 -0
- mainframe_toolkit/pipeline.py +322 -0
- mainframe_toolkit/sequential.py +259 -0
- mainframe_toolkit/similarity.py +1171 -0
- mainframe_toolkit/sorting.py +60 -0
- mainframe_toolkit/specs.py +312 -0
- mainframe_toolkit/synthetic.py +108 -0
- mainframe_toolkit/workspace.py +133 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
package io.mainframe.migration.validator;
|
|
2
|
+
|
|
3
|
+
import org.junit.jupiter.api.Test;
|
|
4
|
+
|
|
5
|
+
import java.io.StringWriter;
|
|
6
|
+
import java.util.List;
|
|
7
|
+
|
|
8
|
+
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
9
|
+
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
10
|
+
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
11
|
+
|
|
12
|
+
class JsonReportWriterTest {
|
|
13
|
+
@Test
|
|
14
|
+
void outputIsStableAndUsesLfOnEveryPlatform() throws Exception {
|
|
15
|
+
ValidationReport report = new ValidationReport(
|
|
16
|
+
"DIFFERENT",
|
|
17
|
+
"expected.csv",
|
|
18
|
+
"actual.csv",
|
|
19
|
+
"CSV",
|
|
20
|
+
"CSV",
|
|
21
|
+
List.of(),
|
|
22
|
+
List.of("trim"),
|
|
23
|
+
1,
|
|
24
|
+
1,
|
|
25
|
+
1,
|
|
26
|
+
1,
|
|
27
|
+
1,
|
|
28
|
+
1,
|
|
29
|
+
false,
|
|
30
|
+
List.of(new Difference(1, 1, "name", " Ana ", "Bia", "VALUE_MISMATCH")));
|
|
31
|
+
JsonReportWriter writer = new JsonReportWriter();
|
|
32
|
+
StringWriter first = new StringWriter();
|
|
33
|
+
StringWriter second = new StringWriter();
|
|
34
|
+
|
|
35
|
+
writer.write(report, first);
|
|
36
|
+
writer.write(report, second);
|
|
37
|
+
|
|
38
|
+
assertEquals(first.toString(), second.toString());
|
|
39
|
+
assertFalse(first.toString().contains("\r"));
|
|
40
|
+
assertTrue(first.toString().endsWith("\n"));
|
|
41
|
+
assertTrue(first.toString().contains("\"line\" : 1"));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
package io.mainframe.migration.validator;
|
|
2
|
+
|
|
3
|
+
import org.junit.jupiter.api.Test;
|
|
4
|
+
import org.junit.jupiter.api.io.TempDir;
|
|
5
|
+
|
|
6
|
+
import java.nio.charset.StandardCharsets;
|
|
7
|
+
import java.nio.file.Files;
|
|
8
|
+
import java.nio.file.Path;
|
|
9
|
+
import java.util.List;
|
|
10
|
+
import java.util.Set;
|
|
11
|
+
|
|
12
|
+
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
13
|
+
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
14
|
+
|
|
15
|
+
class KeyedValidationTest {
|
|
16
|
+
@TempDir
|
|
17
|
+
Path temporaryDirectory;
|
|
18
|
+
|
|
19
|
+
@Test
|
|
20
|
+
void comparesRowsByCompositeKeyRegardlessOfPhysicalOrder() throws Exception {
|
|
21
|
+
Path expected = csv("expected.csv", "account,seq,value\nB,2,x\nA,1,y\n");
|
|
22
|
+
Path actual = csv("actual.csv", "account,seq,value\nA,1,y\nB,2,x\n");
|
|
23
|
+
|
|
24
|
+
ValidationReport report = validate(expected, actual, List.of("account", "seq"), Set.of());
|
|
25
|
+
|
|
26
|
+
assertTrue(report.matches());
|
|
27
|
+
assertEquals(List.of("account", "seq"), report.keys());
|
|
28
|
+
assertEquals(2, report.expectedRows());
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@Test
|
|
32
|
+
void ordersDifferencesByNormalizedKey() throws Exception {
|
|
33
|
+
Path expected = csv("expected.csv", "id,value\nB,expected-b\n A ,expected-a\n");
|
|
34
|
+
Path actual = csv("actual.csv", "id,value\nA,actual-a\nB,actual-b\n");
|
|
35
|
+
|
|
36
|
+
ValidationReport report = validate(
|
|
37
|
+
expected, actual, List.of("id"), Set.of(Normalization.TRIM));
|
|
38
|
+
|
|
39
|
+
assertEquals(2, report.differenceCount());
|
|
40
|
+
assertEquals(" A ", report.differences().get(0).key().get("id"));
|
|
41
|
+
assertEquals("B", report.differences().get(1).key().get("id"));
|
|
42
|
+
assertEquals("value", report.differences().get(0).column());
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@Test
|
|
46
|
+
void reportsDuplicateKeysOnEitherSide() throws Exception {
|
|
47
|
+
Path expected = csv("expected.csv", "id,value\n1,A\n1,A\n2,B\n");
|
|
48
|
+
Path actual = csv("actual.csv", "id,value\n2,B\n1,A\n1,A\n");
|
|
49
|
+
|
|
50
|
+
ValidationReport report = validate(expected, actual, List.of("id"), Set.of());
|
|
51
|
+
|
|
52
|
+
assertEquals(2, report.differenceCount());
|
|
53
|
+
assertEquals("DUPLICATE_EXPECTED_KEY", report.differences().get(0).reason());
|
|
54
|
+
assertEquals("DUPLICATE_ACTUAL_KEY", report.differences().get(1).reason());
|
|
55
|
+
assertEquals("1", report.differences().get(0).key().get("id"));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@Test
|
|
59
|
+
void keyModeWorksAcrossSparkPartDirectories() throws Exception {
|
|
60
|
+
Path expected = Files.createDirectory(temporaryDirectory.resolve("expected-dir"));
|
|
61
|
+
Files.writeString(expected.resolve("part-00000.csv"), "id,value\n2,B\n");
|
|
62
|
+
Files.writeString(expected.resolve("part-00001.csv"), "id,value\n1,A\n");
|
|
63
|
+
Path actual = Files.createDirectory(temporaryDirectory.resolve("actual-dir"));
|
|
64
|
+
Files.writeString(actual.resolve("part-00000.csv"), "id,value\n1,A\n2,B\n");
|
|
65
|
+
|
|
66
|
+
ValidationReport report = validate(expected, actual, List.of("id"), Set.of());
|
|
67
|
+
|
|
68
|
+
assertTrue(report.matches());
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private ValidationReport validate(
|
|
72
|
+
Path expected,
|
|
73
|
+
Path actual,
|
|
74
|
+
List<String> keys,
|
|
75
|
+
Set<Normalization> normalizations) throws Exception {
|
|
76
|
+
InputOptions left = new InputOptions(
|
|
77
|
+
expected, DataFormat.AUTO, ',', true, StandardCharsets.UTF_8);
|
|
78
|
+
InputOptions right = new InputOptions(
|
|
79
|
+
actual, DataFormat.AUTO, ',', true, StandardCharsets.UTF_8);
|
|
80
|
+
return new ValidationService().validate(
|
|
81
|
+
new ValidationOptions(left, right, normalizations, 100, keys));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private Path csv(String name, String content) throws Exception {
|
|
85
|
+
Path path = temporaryDirectory.resolve(name);
|
|
86
|
+
Files.writeString(path, content, StandardCharsets.UTF_8);
|
|
87
|
+
return path;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
package io.mainframe.migration.validator;
|
|
2
|
+
|
|
3
|
+
import org.apache.avro.Schema;
|
|
4
|
+
import org.apache.avro.generic.GenericData;
|
|
5
|
+
import org.apache.avro.generic.GenericRecord;
|
|
6
|
+
import org.apache.parquet.avro.AvroParquetWriter;
|
|
7
|
+
import org.apache.parquet.hadoop.ParquetWriter;
|
|
8
|
+
import org.apache.parquet.hadoop.metadata.CompressionCodecName;
|
|
9
|
+
import org.apache.parquet.io.LocalOutputFile;
|
|
10
|
+
import org.junit.jupiter.api.Test;
|
|
11
|
+
import org.junit.jupiter.api.io.TempDir;
|
|
12
|
+
|
|
13
|
+
import java.nio.charset.StandardCharsets;
|
|
14
|
+
import java.nio.file.Files;
|
|
15
|
+
import java.nio.file.Path;
|
|
16
|
+
import java.util.List;
|
|
17
|
+
import java.util.Set;
|
|
18
|
+
|
|
19
|
+
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
20
|
+
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
21
|
+
|
|
22
|
+
class ParquetValidationTest {
|
|
23
|
+
private static final Schema SCHEMA = new Schema.Parser().parse("""
|
|
24
|
+
{
|
|
25
|
+
"type": "record",
|
|
26
|
+
"name": "Result",
|
|
27
|
+
"fields": [
|
|
28
|
+
{"name": "id", "type": "long"},
|
|
29
|
+
{"name": "amount", "type": "double"},
|
|
30
|
+
{"name": "name", "type": ["null", "string"], "default": null}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
""");
|
|
34
|
+
|
|
35
|
+
@TempDir
|
|
36
|
+
Path temporaryDirectory;
|
|
37
|
+
|
|
38
|
+
@Test
|
|
39
|
+
void comparesParquetCellsInRecordOrder() throws Exception {
|
|
40
|
+
Path expected = parquet("expected.parquet", List.of(
|
|
41
|
+
row(1, 10.5, "Ana"),
|
|
42
|
+
row(2, 20.0, "Bia")));
|
|
43
|
+
Path actual = parquet("actual.parquet", List.of(
|
|
44
|
+
row(1, 10.5, "Ana"),
|
|
45
|
+
row(2, 20.0, "Bea")));
|
|
46
|
+
|
|
47
|
+
ValidationReport report = validate(
|
|
48
|
+
input(expected, DataFormat.AUTO, true),
|
|
49
|
+
input(actual, DataFormat.AUTO, true),
|
|
50
|
+
Set.of());
|
|
51
|
+
|
|
52
|
+
assertEquals(1, report.differenceCount());
|
|
53
|
+
Difference difference = report.differences().get(0);
|
|
54
|
+
assertEquals(2, difference.line());
|
|
55
|
+
assertEquals(3, difference.columnIndex());
|
|
56
|
+
assertEquals("name", difference.column());
|
|
57
|
+
assertEquals("Bia", difference.expected());
|
|
58
|
+
assertEquals("Bea", difference.actual());
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@Test
|
|
62
|
+
void readsSchemaFromEmptyParquetFiles() throws Exception {
|
|
63
|
+
Path expected = parquet("expected.parquet", List.of());
|
|
64
|
+
Path actual = parquet("actual.parquet", List.of());
|
|
65
|
+
|
|
66
|
+
ValidationReport report = validate(
|
|
67
|
+
input(expected, DataFormat.PARQUET, true),
|
|
68
|
+
input(actual, DataFormat.PARQUET, true),
|
|
69
|
+
Set.of());
|
|
70
|
+
|
|
71
|
+
assertTrue(report.matches());
|
|
72
|
+
assertEquals(0, report.expectedRows());
|
|
73
|
+
assertEquals(3, report.expectedColumns());
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@Test
|
|
77
|
+
void comparesCsvAndParquetWithDecimalNormalization() throws Exception {
|
|
78
|
+
Path expected = temporaryDirectory.resolve("expected.csv");
|
|
79
|
+
Files.writeString(expected, "id,amount,name\n1,1.2300,Ana\n", StandardCharsets.UTF_8);
|
|
80
|
+
Path actual = parquet("actual.parquet", List.of(row(1, 1.23, "Ana")));
|
|
81
|
+
|
|
82
|
+
ValidationReport report = validate(
|
|
83
|
+
input(expected, DataFormat.CSV, true),
|
|
84
|
+
input(actual, DataFormat.PARQUET, true),
|
|
85
|
+
Set.of(Normalization.DECIMAL));
|
|
86
|
+
|
|
87
|
+
assertTrue(report.matches());
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@Test
|
|
91
|
+
void readsSparkParquetPartDirectoriesInLexicalOrder() throws Exception {
|
|
92
|
+
Path expected = Files.createDirectory(temporaryDirectory.resolve("expected-dir"));
|
|
93
|
+
parquet(expected.resolve("part-00001.snappy.parquet"), List.of(row(2, 2.0, "Bia")));
|
|
94
|
+
parquet(expected.resolve("part-00000.snappy.parquet"), List.of(row(1, 1.0, "Ana")));
|
|
95
|
+
Files.writeString(expected.resolve("_SUCCESS"), "");
|
|
96
|
+
Path actual = Files.createDirectory(temporaryDirectory.resolve("actual-dir"));
|
|
97
|
+
parquet(actual.resolve("part-00000.parquet"), List.of(
|
|
98
|
+
row(1, 1.0, "Ana"),
|
|
99
|
+
row(2, 2.0, "Bia")));
|
|
100
|
+
|
|
101
|
+
ValidationReport report = validate(
|
|
102
|
+
input(expected, DataFormat.AUTO, true),
|
|
103
|
+
input(actual, DataFormat.AUTO, true),
|
|
104
|
+
Set.of());
|
|
105
|
+
|
|
106
|
+
assertTrue(report.matches());
|
|
107
|
+
assertEquals(2, report.expectedRows());
|
|
108
|
+
assertEquals("PARQUET", report.expectedFormat());
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private ValidationReport validate(
|
|
112
|
+
InputOptions expected,
|
|
113
|
+
InputOptions actual,
|
|
114
|
+
Set<Normalization> normalizations) throws Exception {
|
|
115
|
+
return new ValidationService().validate(new ValidationOptions(expected, actual, normalizations, 100));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private InputOptions input(Path path, DataFormat format, boolean header) {
|
|
119
|
+
return new InputOptions(path, format, ',', header, StandardCharsets.UTF_8);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private GenericRecord row(long id, double amount, String name) {
|
|
123
|
+
GenericRecord record = new GenericData.Record(SCHEMA);
|
|
124
|
+
record.put("id", id);
|
|
125
|
+
record.put("amount", amount);
|
|
126
|
+
record.put("name", name);
|
|
127
|
+
return record;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private Path parquet(String name, List<GenericRecord> records) throws Exception {
|
|
131
|
+
Path path = temporaryDirectory.resolve(name);
|
|
132
|
+
return parquet(path, records);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private Path parquet(Path path, List<GenericRecord> records) throws Exception {
|
|
136
|
+
try (ParquetWriter<GenericRecord> writer = AvroParquetWriter.<GenericRecord>builder(new LocalOutputFile(path))
|
|
137
|
+
.withSchema(SCHEMA)
|
|
138
|
+
.withCompressionCodec(CompressionCodecName.UNCOMPRESSED)
|
|
139
|
+
.build()) {
|
|
140
|
+
for (GenericRecord record : records) {
|
|
141
|
+
writer.write(record);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return path;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
package io.mainframe.migration.validator;
|
|
2
|
+
|
|
3
|
+
import org.junit.jupiter.api.Test;
|
|
4
|
+
import org.junit.jupiter.api.io.TempDir;
|
|
5
|
+
|
|
6
|
+
import java.nio.charset.StandardCharsets;
|
|
7
|
+
import java.nio.file.Files;
|
|
8
|
+
import java.nio.file.Path;
|
|
9
|
+
import java.util.Set;
|
|
10
|
+
|
|
11
|
+
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
12
|
+
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
13
|
+
import static org.junit.jupiter.api.Assertions.assertNull;
|
|
14
|
+
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
15
|
+
|
|
16
|
+
class ValidationServiceTest {
|
|
17
|
+
@TempDir
|
|
18
|
+
Path temporaryDirectory;
|
|
19
|
+
|
|
20
|
+
@Test
|
|
21
|
+
void reportsEveryCellWithItsLineAndColumn() throws Exception {
|
|
22
|
+
Path expected = csv("expected.csv", "id;amount\n1;10.00\n2;ABC\n");
|
|
23
|
+
Path actual = csv("actual.csv", "id;amount\n1;10.01\n2;ABC\n3;X\n");
|
|
24
|
+
|
|
25
|
+
ValidationReport report = validate(expected, actual, ';', true, Set.of(), 20);
|
|
26
|
+
|
|
27
|
+
assertEquals("DIFFERENT", report.status());
|
|
28
|
+
assertEquals(2, report.expectedRows());
|
|
29
|
+
assertEquals(3, report.actualRows());
|
|
30
|
+
assertEquals(3, report.differenceCount());
|
|
31
|
+
Difference first = report.differences().get(0);
|
|
32
|
+
assertEquals(1, first.line());
|
|
33
|
+
assertEquals(2, first.columnIndex());
|
|
34
|
+
assertEquals("amount", first.column());
|
|
35
|
+
assertEquals("10.00", first.expected());
|
|
36
|
+
assertEquals("10.01", first.actual());
|
|
37
|
+
assertEquals("VALUE_MISMATCH", first.reason());
|
|
38
|
+
Difference missingRowCell = report.differences().get(1);
|
|
39
|
+
assertEquals(3, missingRowCell.line());
|
|
40
|
+
assertNull(missingRowCell.expected());
|
|
41
|
+
assertEquals("3", missingRowCell.actual());
|
|
42
|
+
assertEquals("UNEXPECTED_ACTUAL_CELL", missingRowCell.reason());
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@Test
|
|
46
|
+
void comparesNamedColumnsBeforeData() throws Exception {
|
|
47
|
+
Path expected = csv("expected.csv", "id,name\n1,Ana\n");
|
|
48
|
+
Path actual = csv("actual.csv", "id,full_name\n1,Ana\n");
|
|
49
|
+
|
|
50
|
+
ValidationReport report = validate(expected, actual, ',', true, Set.of(), 20);
|
|
51
|
+
|
|
52
|
+
assertEquals(1, report.differenceCount());
|
|
53
|
+
Difference difference = report.differences().get(0);
|
|
54
|
+
assertEquals(0, difference.line());
|
|
55
|
+
assertEquals(2, difference.columnIndex());
|
|
56
|
+
assertEquals("name", difference.expected());
|
|
57
|
+
assertEquals("full_name", difference.actual());
|
|
58
|
+
assertEquals("COLUMN_NAME_MISMATCH", difference.reason());
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@Test
|
|
62
|
+
void appliesTrimAndDecimalNormalizations() throws Exception {
|
|
63
|
+
Path expected = csv("expected.csv", "value\n 001.2300 \n-0.00\ntext \n");
|
|
64
|
+
Path actual = csv("actual.csv", "value\n1.23\n0\ntext\n");
|
|
65
|
+
|
|
66
|
+
ValidationReport report = validate(
|
|
67
|
+
expected,
|
|
68
|
+
actual,
|
|
69
|
+
',',
|
|
70
|
+
true,
|
|
71
|
+
Set.of(Normalization.TRIM, Normalization.DECIMAL),
|
|
72
|
+
20);
|
|
73
|
+
|
|
74
|
+
assertTrue(report.matches());
|
|
75
|
+
assertEquals("MATCH", report.status());
|
|
76
|
+
assertEquals(0, report.differenceCount());
|
|
77
|
+
assertEquals(java.util.List.of("trim", "decimal"), report.normalizations());
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@Test
|
|
81
|
+
void noHeaderKeepsFirstRecordAsData() throws Exception {
|
|
82
|
+
Path expected = csv("expected.csv", "1,Ana\n2,Bia\n");
|
|
83
|
+
Path actual = csv("actual.csv", "1,Ana\n2,Bia\n");
|
|
84
|
+
|
|
85
|
+
ValidationReport report = validate(expected, actual, ',', false, Set.of(), 20);
|
|
86
|
+
|
|
87
|
+
assertTrue(report.matches());
|
|
88
|
+
assertEquals(2, report.expectedRows());
|
|
89
|
+
assertEquals(2, report.expectedColumns());
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@Test
|
|
93
|
+
void limitsDetailsButCountsAllDifferences() throws Exception {
|
|
94
|
+
Path expected = csv("expected.csv", "a,b\n1,2\n3,4\n");
|
|
95
|
+
Path actual = csv("actual.csv", "a,b\nx,y\nz,w\n");
|
|
96
|
+
|
|
97
|
+
ValidationReport report = validate(expected, actual, ',', true, Set.of(), 2);
|
|
98
|
+
|
|
99
|
+
assertEquals(4, report.differenceCount());
|
|
100
|
+
assertEquals(2, report.reportedDifferenceCount());
|
|
101
|
+
assertEquals(2, report.differences().size());
|
|
102
|
+
assertTrue(report.truncated());
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@Test
|
|
106
|
+
void leavesNonNumericCellsUntouchedDuringDecimalNormalization() throws Exception {
|
|
107
|
+
Path expected = csv("expected.csv", "v\n001A\n");
|
|
108
|
+
Path actual = csv("actual.csv", "v\n1A\n");
|
|
109
|
+
|
|
110
|
+
ValidationReport report = validate(
|
|
111
|
+
expected, actual, ',', true, Set.of(Normalization.DECIMAL), 20);
|
|
112
|
+
|
|
113
|
+
assertFalse(report.matches());
|
|
114
|
+
assertEquals("001A", report.differences().get(0).expected());
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private ValidationReport validate(
|
|
118
|
+
Path expected,
|
|
119
|
+
Path actual,
|
|
120
|
+
char delimiter,
|
|
121
|
+
boolean header,
|
|
122
|
+
Set<Normalization> normalizations,
|
|
123
|
+
int maxDifferences) throws Exception {
|
|
124
|
+
InputOptions expectedOptions = new InputOptions(
|
|
125
|
+
expected, DataFormat.AUTO, delimiter, header, StandardCharsets.UTF_8);
|
|
126
|
+
InputOptions actualOptions = new InputOptions(
|
|
127
|
+
actual, DataFormat.AUTO, delimiter, header, StandardCharsets.UTF_8);
|
|
128
|
+
return new ValidationService().validate(
|
|
129
|
+
new ValidationOptions(expectedOptions, actualOptions, normalizations, maxDifferences));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private Path csv(String name, String contents) throws Exception {
|
|
133
|
+
Path path = temporaryDirectory.resolve(name);
|
|
134
|
+
Files.writeString(path, contents, StandardCharsets.UTF_8);
|
|
135
|
+
return path;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
package io.mainframe.migration.validator;
|
|
2
|
+
|
|
3
|
+
import org.junit.jupiter.api.Test;
|
|
4
|
+
import org.junit.jupiter.api.io.TempDir;
|
|
5
|
+
import picocli.CommandLine;
|
|
6
|
+
|
|
7
|
+
import java.io.PrintWriter;
|
|
8
|
+
import java.io.StringWriter;
|
|
9
|
+
import java.nio.file.Files;
|
|
10
|
+
import java.nio.file.Path;
|
|
11
|
+
|
|
12
|
+
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
13
|
+
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
14
|
+
|
|
15
|
+
class ValidatorCliTest {
|
|
16
|
+
@TempDir
|
|
17
|
+
Path temporaryDirectory;
|
|
18
|
+
|
|
19
|
+
@Test
|
|
20
|
+
void returnsZeroForMatchAndWritesJsonToStdout() throws Exception {
|
|
21
|
+
Path expected = write("expected.csv", "v\n 01.00 \n");
|
|
22
|
+
Path actual = write("actual.csv", "v\n1\n");
|
|
23
|
+
StringWriter stdout = new StringWriter();
|
|
24
|
+
CommandLine command = ValidatorCli.commandLine();
|
|
25
|
+
command.setOut(new PrintWriter(stdout, true));
|
|
26
|
+
|
|
27
|
+
int exitCode = command.execute(
|
|
28
|
+
expected.toString(),
|
|
29
|
+
actual.toString(),
|
|
30
|
+
"--normalize", "trim,decimal");
|
|
31
|
+
|
|
32
|
+
assertEquals(ValidatorCli.EXIT_MATCH, exitCode);
|
|
33
|
+
assertTrue(stdout.toString().contains("\"status\" : \"MATCH\""));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@Test
|
|
37
|
+
void returnsOneForDifferencesAndWritesReportFile() throws Exception {
|
|
38
|
+
Path expected = write("expected.data", "id|name\n1|Ana\n");
|
|
39
|
+
Path actual = write("actual.data", "id|name\n1|Bia\n");
|
|
40
|
+
Path report = temporaryDirectory.resolve("reports/result.json");
|
|
41
|
+
CommandLine command = ValidatorCli.commandLine();
|
|
42
|
+
|
|
43
|
+
int exitCode = command.execute(
|
|
44
|
+
expected.toString(),
|
|
45
|
+
actual.toString(),
|
|
46
|
+
"--format", "csv",
|
|
47
|
+
"--delimiter", "pipe",
|
|
48
|
+
"--report", report.toString());
|
|
49
|
+
|
|
50
|
+
assertEquals(ValidatorCli.EXIT_DIFFERENT, exitCode);
|
|
51
|
+
assertTrue(Files.readString(report).contains("\"status\" : \"DIFFERENT\""));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@Test
|
|
55
|
+
void returnsTwoForInvalidInput() {
|
|
56
|
+
StringWriter stderr = new StringWriter();
|
|
57
|
+
CommandLine command = ValidatorCli.commandLine();
|
|
58
|
+
command.setErr(new PrintWriter(stderr, true));
|
|
59
|
+
|
|
60
|
+
int exitCode = command.execute("missing.csv", "also-missing.csv");
|
|
61
|
+
|
|
62
|
+
assertEquals(ValidatorCli.EXIT_INPUT_ERROR, exitCode);
|
|
63
|
+
assertTrue(stderr.toString().contains("Arquivo ou diretório não encontrado"));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@Test
|
|
67
|
+
void supportsNoHeaderAndTabAlias() throws Exception {
|
|
68
|
+
Path expected = write("expected.csv", "1\tAna\n");
|
|
69
|
+
Path actual = write("actual.csv", "1\tAna\n");
|
|
70
|
+
StringWriter stdout = new StringWriter();
|
|
71
|
+
CommandLine command = ValidatorCli.commandLine();
|
|
72
|
+
command.setOut(new PrintWriter(stdout, true));
|
|
73
|
+
|
|
74
|
+
int exitCode = command.execute(
|
|
75
|
+
expected.toString(), actual.toString(), "--delimiter", "tab", "--no-header");
|
|
76
|
+
|
|
77
|
+
assertEquals(ValidatorCli.EXIT_MATCH, exitCode);
|
|
78
|
+
assertTrue(stdout.toString().contains("\"expectedRows\" : 1"), stdout::toString);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@Test
|
|
82
|
+
void acceptsRepeatedAndCommaSeparatedKeys() throws Exception {
|
|
83
|
+
Path expected = write("expected.csv", "tenant,id,value\nB,2,x\nA,1,y\n");
|
|
84
|
+
Path actual = write("actual.csv", "tenant,id,value\nA,1,y\nB,2,x\n");
|
|
85
|
+
StringWriter stdout = new StringWriter();
|
|
86
|
+
CommandLine command = ValidatorCli.commandLine();
|
|
87
|
+
command.setOut(new PrintWriter(stdout, true));
|
|
88
|
+
|
|
89
|
+
int exitCode = command.execute(
|
|
90
|
+
expected.toString(), actual.toString(), "--key", "tenant,id");
|
|
91
|
+
|
|
92
|
+
assertEquals(ValidatorCli.EXIT_MATCH, exitCode);
|
|
93
|
+
assertTrue(stdout.toString().contains("\"tenant\""), stdout::toString);
|
|
94
|
+
assertTrue(stdout.toString().contains("\"id\""), stdout::toString);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private Path write(String name, String content) throws Exception {
|
|
98
|
+
Path path = temporaryDirectory.resolve(name);
|
|
99
|
+
Files.writeString(path, content);
|
|
100
|
+
return path;
|
|
101
|
+
}
|
|
102
|
+
}
|