Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 109 additions & 18 deletions .github/workflows/compatibility-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,25 @@ on:
paths:
- '.github/workflows/compatibility-test.yml'
- '.mvn/**'
- 'cpp/**'
- 'java/**'
- 'mvnw'
- 'pom.xml'
# The binary-format surface: the Java/C++ sources that define how data
# is encoded, compressed, and laid out on disk, plus the module POMs
# that select/version the compression dependencies, and the compatibility
# harness itself. Unrelated test/example/tool/doc changes are excluded
# so a routine PR does not pay the ~30 minute cross-language matrix.
- 'compat/**'
- 'java/common/src/main/**'
- 'java/tsfile/src/main/**'
- 'java/pom.xml'
- 'java/common/pom.xml'
- 'java/tsfile/pom.xml'
- 'java/tsfile/src/test/java/org/apache/tsfile/compatibility/**'
- 'cpp/src/**'
- 'cpp/CMakeLists.txt'
- 'cpp/cmake/**'
- 'cpp/third_party/**'
- 'cpp/test/reader/table_view/encoding_compression_compatibility_test.cc'
pull_request:
branches:
- develop
Expand All @@ -39,10 +54,25 @@ on:
paths:
- '.github/workflows/compatibility-test.yml'
- '.mvn/**'
- 'cpp/**'
- 'java/**'
- 'mvnw'
- 'pom.xml'
# The binary-format surface: the Java/C++ sources that define how data
# is encoded, compressed, and laid out on disk, plus the module POMs
# that select/version the compression dependencies, and the compatibility
# harness itself. Unrelated test/example/tool/doc changes are excluded
# so a routine PR does not pay the ~30 minute cross-language matrix.
- 'compat/**'
- 'java/common/src/main/**'
- 'java/tsfile/src/main/**'
- 'java/pom.xml'
- 'java/common/pom.xml'
- 'java/tsfile/pom.xml'
- 'java/tsfile/src/test/java/org/apache/tsfile/compatibility/**'
- 'cpp/src/**'
Comment thread
ColinLeeo marked this conversation as resolved.
Resolved
- 'cpp/CMakeLists.txt'
- 'cpp/cmake/**'
- 'cpp/third_party/**'
- 'cpp/test/reader/table_view/encoding_compression_compatibility_test.cc'
workflow_dispatch:
inputs:
forceUpdates:
Expand All @@ -63,13 +93,16 @@ env:

jobs:
generate-fixtures:
name: Generate ${{ matrix.language }} fixtures
name: Generate ${{ matrix.language }} fixtures (shard ${{ matrix.shard }})
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
language: [ java, cpp ]
# The full matrix is split into shards so each generation job stays
# within the timeout; the shards run in parallel.
shard: [ 0, 1, 2 ]
steps:
- name: Checkout repository
uses: actions/checkout@v7
Expand All @@ -94,17 +127,28 @@ jobs:
path: cpp/target/dependency-cache
key: ${{ runner.os }}-tsfile-cpp-deps-${{ hashFiles('cpp/CMakeLists.txt', 'cpp/cmake/*Source.cmake', 'cpp/third_party/CMakeLists.txt') }}

- name: Generate Java table fixtures
- name: Generate Java compatibility fixtures
if: matrix.language == 'java'
shell: bash
run: |
mkdir -p compat-fixtures/java
./mvnw -P with-java \
-Dtest=TableModelEncodingCompressionCompatibilityTest#generateFixtures \
-Dtest=EncodingCompressionCompatibilityTest#generateFixtures \
-Dsurefire.failIfNoSpecifiedTests=false \
-Dtsfile.compat.spec="$PWD/compat/compatibility-matrix.csv" \
-Dtsfile.compat.generate.dir="$PWD/compat-fixtures/java" \
-Dtsfile.compat.shardIndex="${{ matrix.shard }}" \
-Dtsfile.compat.shardCount=3 \
test

- name: Publish Java generation summary
if: matrix.language == 'java' && always()
shell: bash
run: |
if [ -f compat-fixtures/java/summary.md ]; then
cat compat-fixtures/java/summary.md >> "$GITHUB_STEP_SUMMARY"
fi

- name: Build C++ tests
if: matrix.language == 'cpp'
shell: bash
Expand All @@ -115,34 +159,45 @@ jobs:
-Dspotless.skip=true \
-Dtsfile.dependency.source=BUNDLED \
-Denable.antlr4=OFF \
-Denable.snappy=OFF \
-Denable.snappy=ON \
-Denable.lz4=ON \
-Denable.lzokay=OFF \
-Denable.zlib=OFF \
-Denable.zlib=ON \
-Denable.zstd=ON \
-Denable.lzma2=ON \
-Denable.simd=OFF \
package

- name: Generate C++ table fixtures
- name: Generate C++ compatibility fixtures
if: matrix.language == 'cpp'
shell: bash
run: |
mkdir -p compat-fixtures/cpp
TSFILE_COMPAT_SPEC="$PWD/compat/compatibility-matrix.csv" \
TSFILE_COMPAT_GENERATE_DIR="$PWD/compat-fixtures/cpp" \
TSFILE_COMPAT_SHARD_INDEX="${{ matrix.shard }}" \
TSFILE_COMPAT_SHARD_COUNT=3 \
./cpp/target/build/test/lib/TsFile_Test \
--gtest_filter=TableModelEncodingCompressionCompatibilityTest.GenerateFixtures
--gtest_filter=EncodingCompressionCompatibilityTest.GenerateFixtures

- name: Publish C++ generation summary
if: matrix.language == 'cpp' && always()
shell: bash
run: |
if [ -f compat-fixtures/cpp/summary.md ]; then
cat compat-fixtures/cpp/summary.md >> "$GITHUB_STEP_SUMMARY"
fi

- name: Upload ${{ matrix.language }} fixtures
uses: actions/upload-artifact@v7
with:
name: tsfile-${{ matrix.language }}-compat-fixtures
name: tsfile-${{ matrix.language }}-compat-fixtures-shard-${{ matrix.shard }}
path: compat-fixtures/${{ matrix.language }}/
if-no-files-found: error
retention-days: 1

validate-fixtures:
name: Validate ${{ matrix.writer }} fixtures with ${{ matrix.reader }}
name: Validate ${{ matrix.writer }} shard ${{ matrix.shard }} with ${{ matrix.reader }}
needs: generate-fixtures
runs-on: ubuntu-latest
timeout-minutes: 30
Expand All @@ -152,8 +207,22 @@ jobs:
include:
- writer: java
reader: cpp
shard: 0
- writer: java
reader: cpp
shard: 1
- writer: java
reader: cpp
shard: 2
- writer: cpp
reader: java
shard: 0
- writer: cpp
reader: java
shard: 1
- writer: cpp
reader: java
shard: 2
steps:
- name: Checkout repository
uses: actions/checkout@v7
Expand Down Expand Up @@ -181,19 +250,30 @@ jobs:
- name: Download ${{ matrix.writer }} fixtures
uses: actions/download-artifact@v8
with:
name: tsfile-${{ matrix.writer }}-compat-fixtures
name: tsfile-${{ matrix.writer }}-compat-fixtures-shard-${{ matrix.shard }}
path: compat-fixtures/${{ matrix.writer }}

- name: Validate ${{ matrix.writer }} fixtures with Java
if: matrix.reader == 'java'
shell: bash
run: |
./mvnw -P with-java \
-Dtest=TableModelEncodingCompressionCompatibilityTest#validateFixtures \
-Dtest=EncodingCompressionCompatibilityTest#validateFixtures \
-Dsurefire.failIfNoSpecifiedTests=false \
-Dtsfile.compat.spec="$PWD/compat/compatibility-matrix.csv" \
-Dtsfile.compat.validate.dir="$PWD/compat-fixtures/${{ matrix.writer }}" \
-Dtsfile.compat.shardIndex="${{ matrix.shard }}" \
-Dtsfile.compat.shardCount=3 \
test

- name: Publish Java validation summary
if: matrix.reader == 'java' && always()
shell: bash
run: |
if [ -f compat-fixtures/${{ matrix.writer }}/summary.md.validate ]; then
cat compat-fixtures/${{ matrix.writer }}/summary.md.validate >> "$GITHUB_STEP_SUMMARY"
fi

- name: Build C++ tests
if: matrix.reader == 'cpp'
shell: bash
Expand All @@ -204,10 +284,10 @@ jobs:
-Dspotless.skip=true \
-Dtsfile.dependency.source=BUNDLED \
-Denable.antlr4=OFF \
-Denable.snappy=OFF \
-Denable.snappy=ON \
-Denable.lz4=ON \
-Denable.lzokay=OFF \
-Denable.zlib=OFF \
-Denable.zlib=ON \
-Denable.zstd=ON \
-Denable.lzma2=ON \
-Denable.simd=OFF \
Expand All @@ -217,6 +297,17 @@ jobs:
if: matrix.reader == 'cpp'
shell: bash
run: |
TSFILE_COMPAT_SPEC="$PWD/compat/compatibility-matrix.csv" \
TSFILE_COMPAT_VALIDATE_DIR="$PWD/compat-fixtures/${{ matrix.writer }}" \
TSFILE_COMPAT_SHARD_INDEX="${{ matrix.shard }}" \
TSFILE_COMPAT_SHARD_COUNT=3 \
./cpp/target/build/test/lib/TsFile_Test \
--gtest_filter=TableModelEncodingCompressionCompatibilityTest.ValidateFixtures
--gtest_filter=EncodingCompressionCompatibilityTest.ValidateFixtures

- name: Publish C++ validation summary
if: matrix.reader == 'cpp' && always()
shell: bash
run: |
if [ -f compat-fixtures/${{ matrix.writer }}/summary.md.validate ]; then
cat compat-fixtures/${{ matrix.writer }}/summary.md.validate >> "$GITHUB_STEP_SUMMARY"
fi
139 changes: 139 additions & 0 deletions compat/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<!--

Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.

-->

# Java / C++ cross-language compatibility fixtures

`compatibility-matrix.csv` is the single source of truth for the fixtures that
the Java and C++ implementations write for each other. Both test suites read
this file:

- Java: `java/tsfile/src/test/java/org/apache/tsfile/compatibility/EncodingCompressionCompatibilityTest.java`
- C++: `cpp/test/reader/table_view/encoding_compression_compatibility_test.cc`

Each side writes every case it declares writable, reads back its own fixtures,
and validates fixtures produced by the other language. The CI workflow
`.github/workflows/compatibility-test.yml` runs generation in one job per
language and cross-validation in both directions, then publishes the coverage
summary.

## Columns

| Column | Meaning |
| --- | --- |
| `id` | Stable case id. Expanded cases get `.` + compression + `.` + null pattern. The fixture file is `<expanded id>.tsfile`. |
| `model` | `table` (Tag/Field table model) or `tree` (device/measurement model). |
| `dataType` | `BOOLEAN`, `INT32`, `INT64`, `TIMESTAMP`, `DATE`, `FLOAT`, `DOUBLE`, `TEXT`, `STRING`, `BLOB`. |
| `encoding` | `TSEncoding` enum name. |
| `compressions` | `ALL` (the six codecs both projects ship: UNCOMPRESSED+SNAPPY+GZIP+LZ4+ZSTD+LZMA2) or a `+`-separated subset. |
| `rowCount` | Number of timestamps per device. |
| `pageRows` | Points per page; `0` keeps the writer default. |
| `chunks` | Number of chunks to produce by flushing between sub-batches. |
| `devices` | Number of devices written into one file (one chunk group per device). |
| `valueProfile` | Deterministic value set, see below. |
| `nullPatterns` | `|`-separated `NONE`, `MIXED`, `ALL`. |
| `javaWrite`/`cppWrite` | Whether that language writes the fixture. |
| `javaRead`/`cppRead` | Whether that language validates the fixture when the other side wrote it. |
| `gapReason` | Required whenever any write/read cell is `no`; the reason is reported in every CI run. |

A case with a `no` in a write column is an explicit declared gap. A case absent
from the file is not a gap: the completeness test in both suites enumerates the
cartesian product of every data type and encoding the local writer supports and
fails when the spec does not cover it. That is what keeps the matrix complete
as new encodings are added.

## Data scale and page-size tiers

Every (type, encoding, compression) combination is generated at four data
scales — 10, 1000, 100000 and 1000000 rows — with a page size that crosses
page boundaries at that scale. To keep each CI job within its timeout the
fixture generation is sharded: the test takes a `shardIndex`/`shardCount` and
only emits the cases whose index falls into that shard, and the CI matrix runs
one job per shard in parallel.

| Scale | Page size | Page structure it exercises |
| --- | --- | --- |
| 10 rows | 4 | 3 pages with a partial last page (4+4+2) |
| 1000 rows | 100 | 10 pages |
| 100000 rows | 10000 | 10 pages (writer default) |
| 1000000 rows | 10000 | 100 pages (writer default) |

`UNCOMPRESSED` is dropped at the 1M tier: a million raw values (or long
repeated strings) is a multi-megabyte file per case for no extra coverage, and
it would blow the CI artifact budget. It is still fully covered at the three
smaller scales.

A reduced encoding set (`PLAIN`, `TS_2DIFF`, `RLE`) also gets a "many pages"
variant — page 100 at 100000 rows, page 1000 at 1000000 rows — which produces
1000 pages and crosses the 256-entry index-node fanout, stressing the
multi-level metadata index.

## Value profiles

Values are a pure function of the profile and the row index, implemented
identically in Java and C++. Expected values apply the same quantization the
writer performs, so a fixture has one expected result no matter which language
wrote it.

| Profile | Purpose |
| --- | --- |
| `default` | Type-specific values including numeric extremes and repeated values. |
| `ts2diff` | FLOAT/DOUBLE TS_2DIFF values covering scaled, scale-overflow and raw-bit forms (NaN, infinities) across the 129-value block boundary. |
| `ts2diff_mpn0` | Same values written with an explicit `max_point_number=0` property (Java only; the C++ writer has no property path). |
| `rle_overflow` | FLOAT/DOUBLE RLE with 8-value runs in 64-row pages: ordinary scaled values, scaled overflow only, mixed scaled/unscaled/raw IEEE values (NaN, infinities and finite extremes), then ordinary values again and a single-row final page. |
| `sprintz` | Values inside a range the SPRINTZ FIR predictor can encode (about ±1e8 for INT32 and ±1e12 for INT64). Outside it the predictor itself overflows, which is a codec limitation rather than a cross-language difference. |
| `runs` | Long runs for RLE encodings. |
| `strings` | Empty string, embedded NUL, multi-byte UTF-8, repeated values, long values. |
| `dict_high` | One distinct string per row to stress dictionary growth. |
| `blob` | Empty, all-zero, binary and text-like byte arrays. |

The `rle_overflow` fixtures cover both models and all six compressions in both
directions. The normal unit suites also verify their three page-header forms
and reuse the encoder/decoder across pages, so overflow state cannot leak into
the following ordinary page unnoticed.

## Running locally

```bash
# Java writes, Java and C++ read
./mvnw -P with-java \
-Dtest=EncodingCompressionCompatibilityTest#generateFixtures \
-Dsurefire.failIfNoSpecifiedTests=false \
-Dtsfile.compat.spec="$PWD/compat/compatibility-matrix.csv" \
-Dtsfile.compat.generate.dir="$PWD/compat-fixtures/java" test

TSFILE_COMPAT_SPEC="$PWD/compat/compatibility-matrix.csv" \
TSFILE_COMPAT_VALIDATE_DIR="$PWD/compat-fixtures/java" \
./cpp/target/build/test/lib/TsFile_Test \
--gtest_filter=EncodingCompressionCompatibilityTest.ValidateFixtures
```

Swap the environments for the C++ → Java direction. Both suites also emit
`report.csv` next to the fixtures with a per-case status, which the CI job
summary aggregates.

## Adding coverage

1. Add a row (or edit an existing one) in `compatibility-matrix.csv`.
2. If a language cannot write or read the new combination, set the column to
`no` and write the reason. Link a tracking issue when the gap is a defect.
3. Run both directions locally; the generator fails when a declared case cannot
be produced and the validator fails when a declared-readable fixture cannot
be read.
Loading
Loading