Skip to content

Commit b14e0df

Browse files
authored
Merge pull request #411 from InseeFr/Feat/TCK
Feat/tck
2 parents 666bf74 + 22c8761 commit b14e0df

File tree

18 files changed

+574
-12
lines changed

18 files changed

+574
-12
lines changed

.github/workflows/tck-vtl-tf.yml

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
name: Run VTL TF TCK
2+
3+
on:
4+
push:
5+
branches: [ '**' ]
6+
pull_request:
7+
branches: [ master, develop ]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout main project
15+
uses: actions/checkout@v4
16+
with:
17+
fetch-depth: 0
18+
19+
- name: Clone vtl spec repo (branch fix/tck-2.1)
20+
run: git clone --branch fix/tck-2.1 https://github.com/sdmx-twg/vtl.git
21+
22+
- name: Install Python 3
23+
uses: actions/setup-python@v5
24+
with:
25+
python-version: '3.11'
26+
27+
- name: Run TCK generator script
28+
run: |
29+
DOC_VERSION=v2.1 python3 vtl/scripts/generate_tck_files.py
30+
31+
- name: Move generated TCK zip to resources
32+
run: |
33+
mkdir -p coverage/src/main/resources
34+
mv vtl/tck/v2.1.zip coverage/src/main/resources/
35+
36+
- name: Set up Java
37+
uses: actions/setup-java@v4
38+
with:
39+
distribution: 'temurin'
40+
java-version: '17'
41+
42+
- uses: s4u/maven-settings-action@v3.0.0
43+
with:
44+
githubServer: false
45+
servers: |
46+
[{
47+
"id": "Github",
48+
"username": "${{ secrets.GH_PACKAGES_USERNAME }}",
49+
"password": "${{ secrets.GH_PACKAGES_PASSWORD }}"
50+
}]
51+
52+
- name: Cache Maven packages
53+
uses: actions/cache@v4
54+
with:
55+
path: ~/.m2/repository
56+
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
57+
restore-keys: |
58+
${{ runner.os }}-maven-
59+
60+
- name: Build and run tests
61+
run: mvn clean test --batch-mode
62+
63+
- name: Publish JUnit test results
64+
uses: dorny/test-reporter@v2
65+
if: always()
66+
with:
67+
name: JUnit Test Report
68+
path: coverage/target/surefire-reports/*.xml
69+
reporter: java-junit
70+
fail-on-error: 'false'

coverage/README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# 📊 Coverage
2+
3+
## 🧪 TCK (Technology Compatibility Kit)
4+
5+
We are working on a suite of compatibility tests to ensure conformance with the VTL specification across implementations.
6+
7+
🛠️ _Work in Progress_
8+
9+
### Issues/discussions to follow
10+
11+
- [junit5](https://github.com/junit-team/junit5/discussions/4504#discussioncomment-13046641)
12+
- [surefire](https://github.com/apache/maven-surefire/issues/835)
13+
14+
### Temporary run procedure
15+
16+
While [TCK](https://github.com/sdmx-twg/vtl/pull/565) is not automated in the VTL TF repository, we have to build the input source manually.
17+
18+
```shell
19+
git clone https://github.com/sdmx-twg/vtl.git
20+
cd vtl/scripts
21+
DOC_VERSION=v2.1 python3 generate_tck_files.py
22+
```
23+
24+
A zip will be created at `tck/v2.1.zip`.
25+
26+
Move it in Trevas resources:
27+
28+
```shell
29+
mv vtl/tck/v2.1.zip trevas/coverage/src/main/resources
30+
```
31+
32+
You are now able to run `TCKTest`.

coverage/pom.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,27 @@
4949
<artifactId>vtl-spark</artifactId>
5050
<version>1.9.0-SNAPSHOT</version>
5151
</dependency>
52+
<dependency>
53+
<groupId>fr.insee.trevas</groupId>
54+
<artifactId>vtl-csv</artifactId>
55+
<version>1.9.0-SNAPSHOT</version>
56+
</dependency>
57+
<dependency>
58+
<groupId>com.fasterxml.jackson.core</groupId>
59+
<artifactId>jackson-databind</artifactId>
60+
<version>2.15.2</version>
61+
</dependency>
5262
</dependencies>
5363
<build>
5464
<plugins>
65+
<plugin>
66+
<groupId>org.apache.maven.plugins</groupId>
67+
<artifactId>maven-surefire-plugin</artifactId>
68+
<version>3.5.3</version>
69+
<configuration>
70+
<argLine>--add-exports java.base/sun.nio.ch=ALL-UNNAMED</argLine>
71+
</configuration>
72+
</plugin>
5573
<plugin>
5674
<groupId>org.jacoco</groupId>
5775
<artifactId>jacoco-maven-plugin</artifactId>
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package fr.insee.vtl.coverage;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import fr.insee.vtl.coverage.model.Folder;
5+
import fr.insee.vtl.coverage.model.Test;
6+
import fr.insee.vtl.coverage.utils.JSONStructureLoader;
7+
8+
import java.io.File;
9+
import java.io.FileInputStream;
10+
import java.io.IOException;
11+
import java.io.InputStream;
12+
import java.nio.charset.StandardCharsets;
13+
import java.nio.file.Files;
14+
import java.nio.file.Path;
15+
import java.nio.file.StandardCopyOption;
16+
import java.util.*;
17+
import java.util.zip.ZipEntry;
18+
import java.util.zip.ZipInputStream;
19+
20+
public class TCK {
21+
private static final ObjectMapper objectMapper = new ObjectMapper();
22+
23+
public static List<Folder> runTCK(InputStream zipInputStream) {
24+
File extractedFolder;
25+
try {
26+
extractedFolder = init(zipInputStream);
27+
} catch (IOException e) {
28+
throw new RuntimeException("Error unzipping input stream", e);
29+
}
30+
31+
try {
32+
return loadInput(extractedFolder);
33+
} catch (Exception e) {
34+
throw new RuntimeException("Error loading input from extracted folder", e);
35+
} finally {
36+
deleteDirectory(extractedFolder);
37+
}
38+
}
39+
40+
public static List<Folder> runTCK(File zipFile) {
41+
try (InputStream in = Files.newInputStream(zipFile.toPath())) {
42+
return runTCK(in);
43+
} catch (IOException e) {
44+
throw new RuntimeException("Error reading zip file: " + zipFile, e);
45+
}
46+
}
47+
48+
public static List<Folder> runTCK(String zipPath) {
49+
return runTCK(new File(zipPath));
50+
}
51+
52+
private static File init(InputStream zipInputStream) throws IOException {
53+
Path tempDir = Files.createTempDirectory("tck-unzip-");
54+
try (ZipInputStream zis = new ZipInputStream(zipInputStream)) {
55+
ZipEntry entry;
56+
while ((entry = zis.getNextEntry()) != null) {
57+
Path newPath = zipSlipProtect(entry, tempDir);
58+
if (entry.isDirectory()) {
59+
Files.createDirectories(newPath);
60+
} else {
61+
Files.createDirectories(newPath.getParent());
62+
Files.copy(zis, newPath, StandardCopyOption.REPLACE_EXISTING);
63+
}
64+
}
65+
}
66+
return tempDir.toFile();
67+
}
68+
69+
private static Path zipSlipProtect(ZipEntry entry, Path targetDir) throws IOException {
70+
Path target = targetDir.resolve(entry.getName()).normalize();
71+
if (!target.startsWith(targetDir)) {
72+
throw new IOException("Entry is outside of the target dir: " + entry.getName());
73+
}
74+
return target;
75+
}
76+
77+
private static void deleteDirectory(File dir) {
78+
if (dir.isDirectory()) {
79+
for (File file : Objects.requireNonNull(dir.listFiles())) {
80+
deleteDirectory(file);
81+
}
82+
}
83+
dir.delete();
84+
}
85+
86+
public static List<Folder> loadInput(File path) throws Exception {
87+
List<Folder> folders = new ArrayList<>();
88+
File[] files = path.listFiles();
89+
if (files != null) {
90+
boolean isTestFolder = containsTestFiles(files);
91+
92+
if (isTestFolder) {
93+
Folder folder = new Folder();
94+
folder.setName(path.getName());
95+
Test test = new Test();
96+
97+
for (File file : files) {
98+
switch (file.getName()) {
99+
case "input.json":
100+
test.setInput(JSONStructureLoader.loadDatasetsFromCSV(file));
101+
break;
102+
case "output.json":
103+
test.setOutputs(JSONStructureLoader.loadDatasetsFromCSV(file));
104+
break;
105+
case "transformation.vtl":
106+
String script = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
107+
test.setScript(script);
108+
break;
109+
}
110+
}
111+
folder.setTest(test);
112+
folders.add(folder);
113+
} else {
114+
for (File file : files) {
115+
if (file.isDirectory()) {
116+
Folder folder = new Folder();
117+
folder.setName(file.getName());
118+
folder.setFolders(loadInput(file));
119+
folders.add(folder);
120+
}
121+
}
122+
}
123+
}
124+
return folders;
125+
}
126+
127+
private static boolean containsTestFiles(File[] files) {
128+
Set<String> required = new HashSet<>(Arrays.asList(
129+
"input.json", "output.json", "transformation.vtl"
130+
));
131+
Set<String> found = new HashSet<>();
132+
for (File file : files) {
133+
if (required.contains(file.getName())) {
134+
found.add(file.getName());
135+
}
136+
}
137+
return found.containsAll(required);
138+
}
139+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package fr.insee.vtl.coverage.model;
2+
3+
import java.util.List;
4+
5+
public class Folder {
6+
7+
private String name;
8+
private List<Folder> folders;
9+
private Test test;
10+
11+
public String getName() {
12+
return name;
13+
}
14+
15+
public void setName(String name) {
16+
this.name = name;
17+
}
18+
19+
public Test getTest() {
20+
return test;
21+
}
22+
23+
public void setTest(Test test) {
24+
this.test = test;
25+
}
26+
27+
public List<Folder> getFolders() {
28+
return folders;
29+
}
30+
31+
public void setFolders(List<Folder> folders) {
32+
this.folders = folders;
33+
}
34+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package fr.insee.vtl.coverage.model;
2+
3+
import fr.insee.vtl.model.Dataset;
4+
5+
import java.util.Map;
6+
7+
public class Test {
8+
9+
private String script;
10+
private Map<String, Dataset> input;
11+
private Map<String, Dataset> outputs;
12+
13+
public String getScript() {
14+
return script;
15+
}
16+
17+
public void setScript(String script) {
18+
this.script = script;
19+
}
20+
21+
public Map<String, Dataset> getInput() {
22+
return input;
23+
}
24+
25+
public void setInput(Map<String, Dataset> input) {
26+
this.input = input;
27+
}
28+
29+
public Map<String, Dataset> getOutputs() {
30+
return outputs;
31+
}
32+
33+
public void setOutputs(Map<String, Dataset> outputs) {
34+
this.outputs = outputs;
35+
}
36+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/**
2+
* This package contains tools for TCK.
3+
*/
4+
package fr.insee.vtl.coverage;

0 commit comments

Comments
 (0)