Skip to content

Commit a950ce3

Browse files
committed
feat: release v1.0.1 with t-wise support, constraint fixes, and coverage engine improvements
- Refactor coverage engine with ForEachTuple helper and overflow-safe tuple counting - Fix greedy algorithm constraint-aware fallback - Add full Unicode escape support to CLI JSON parser - Mirror all C++ changes in TypeScript engine - Add t-wise strength parameter to JS API - Update bilingual documentation - Bump TypeScript target to ES2022 for Object.hasOwn support - Bump version to 1.0.1
1 parent e126bca commit a950ce3

44 files changed

Lines changed: 837 additions & 454 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
cmake_minimum_required(VERSION 3.16)
2-
project(coverwise VERSION 1.0.0 LANGUAGES CXX)
2+
project(coverwise VERSION 1.0.1 LANGUAGES CXX)
33

44
set(CMAKE_CXX_STANDARD 17)
55
set(CMAKE_CXX_STANDARD_REQUIRED ON)

docs/en/examples.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ const result = cw.generate({
7373
});
7474

7575
console.log('Positive tests:', result.tests.length);
76-
console.log('Negative tests:', result.negativeTests?.length);
76+
console.log('Negative tests:', result.negativeTests.length);
7777

7878
// Positive tests cover valid combinations only.
7979
// Negative tests each have exactly 1 invalid value.

docs/en/introduction.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ coverwise is built as a **test design API**, not just a generation tool:
6969
- **Stateless**`generate(input) → output`, no session management
7070
- **Decomposable** — Separate functions for generate, analyze, extend
7171
- **Explainable** — Every result includes coverage proof and uncovered tuples in human-readable format
72-
- **Deterministic** — Same input + seed = same output, every time
72+
- **Deterministic** — Same input + seed = same output, every time (within the same engine; WASM and Pure TS use different RNG algorithms, so the same seed may produce different test orderings across engines while maintaining identical coverage)
7373

7474
## Platform Support
7575

docs/en/js-api.md

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Both styles share the same WASM singleton and are interchangeable.
2424

2525
## `init()`
2626

27-
Initialize the WASM module. Must be called before any other function. Safe to call multiple times — the module loads only once.
27+
Initialize the WASM module. Must be called before any other function. Safe to call multiple times — the module loads only once. If initialization fails (e.g., WASM file not found), subsequent calls will retry instead of caching the failure.
2828

2929
```typescript
3030
async function init(): Promise<void>
@@ -44,8 +44,8 @@ function generate(input: GenerateInput): GenerateResult
4444
interface GenerateInput {
4545
parameters: Parameter[]; // Required. At least 1 parameter.
4646
constraints?: string[]; // Constraint expressions.
47-
strength?: number; // Interaction strength. Default: 2 (pairwise).
48-
seed?: number; // RNG seed for determinism. Default: 0.
47+
strength?: number; // Interaction strength (positive integer). Default: 2 (pairwise).
48+
seed?: number; // RNG seed for determinism (finite number). Default: 0.
4949
maxTests?: number; // Max test count. 0 = no limit (default).
5050
weights?: WeightConfig; // Value weight hints.
5151
seeds?: TestCase[]; // Existing test cases to build upon.
@@ -146,12 +146,12 @@ generate({
146146
```typescript
147147
interface GenerateResult {
148148
tests: TestCase[]; // Positive test cases (no invalid values).
149-
negativeTests?: TestCase[]; // Negative tests (exactly 1 invalid value each).
149+
negativeTests: TestCase[]; // Negative tests (exactly 1 invalid value each). Empty array if none.
150150
coverage: number; // Coverage ratio (0.0 – 1.0).
151151
uncovered: UncoveredTuple[]; // Uncovered tuples with reasons.
152152
stats: GenerateStats;
153153
suggestions: Suggestion[]; // Actionable suggestions.
154-
warnings: string[]; // Performance/configuration warnings.
154+
warnings: string[]; // Warnings (e.g., coverage < 100%, seed count exceeds maxTests).
155155
strength: number; // Actual strength used.
156156
classCoverage?: ClassCoverage; // Present when equivalence classes are defined.
157157
}
@@ -288,6 +288,17 @@ const result = cw.generate({
288288

289289
See [Constraint Syntax](constraints.md) for the full builder API reference.
290290

291+
## Input Validation
292+
293+
The Pure TypeScript API validates inputs and throws descriptive errors for:
294+
295+
- `strength`: Must be a positive integer. Non-integer, negative, or zero values are rejected.
296+
- `seed`: Must be a finite number. `NaN` and `Infinity` are rejected.
297+
- `maxTests`: Must be a non-negative integer when provided.
298+
- `parameters`: Must be a non-empty array.
299+
300+
The WASM API performs equivalent validation at the C++ boundary.
301+
291302
## Error Handling
292303

293304
Functions throw `CoverwiseError` on invalid input:

docs/ja/examples.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ const result = cw.generate({
7373
});
7474

7575
console.log('正常テスト:', result.tests.length);
76-
console.log('ネガティブテスト:', result.negativeTests?.length);
76+
console.log('ネガティブテスト:', result.negativeTests.length);
7777

7878
// 正常テストは有効な組み合わせのみ。
7979
// ネガティブテストはそれぞれ無効値が正確に1つ。

docs/ja/introduction.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ coverwise は単なる生成ツールではなく、**テスト設計API** と
6969
- **ステートレス**`generate(input) → output`、セッション管理不要
7070
- **分解可能** — 生成・分析・拡張の独立した関数
7171
- **説明可能** — すべての結果にカバレッジ証明と人間が読める未カバータプルを含む
72-
- **決定的** — 同じ入力+シード=毎回同じ出力
72+
- **決定的** — 同じ入力+シード=毎回同じ出力(同一エンジン内での保証。WASMとPure TSは異なるRNGアルゴリズムを使用するため、同じシードでもエンジン間ではテスト順序が異なる場合がありますが、カバレッジは同等です)
7373

7474
## プラットフォームサポート
7575

docs/ja/js-api.md

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const result = generate({ parameters: [...] });
2424

2525
## `init()`
2626

27-
WASM モジュールを初期化します。他の関数を呼ぶ前に必ず呼んでください。複数回呼んでも安全です。モジュールは一度だけロードされます。
27+
WASM モジュールを初期化します。他の関数を呼ぶ前に必ず呼んでください。複数回呼んでも安全です。モジュールは一度だけロードされます。初期化に失敗した場合(例:WASMファイルが見つからない)、以降の呼び出しは失敗をキャッシュせずリトライします。
2828

2929
```typescript
3030
async function init(): Promise<void>
@@ -44,8 +44,8 @@ function generate(input: GenerateInput): GenerateResult
4444
interface GenerateInput {
4545
parameters: Parameter[]; // 必須。1つ以上のパラメータ。
4646
constraints?: string[]; // 制約式。
47-
strength?: number; // 相互作用の強度。デフォルト: 2(ペアワイズ)。
48-
seed?: number; // 決定性のためのRNGシード。デフォルト: 0。
47+
strength?: number; // 相互作用の強度(正の整数)。デフォルト: 2(ペアワイズ)。
48+
seed?: number; // 決定性のためのRNGシード(有限数値)。デフォルト: 0。
4949
maxTests?: number; // 最大テスト数。0 = 無制限(デフォルト)。
5050
weights?: WeightConfig; // 値の重み付けヒント。
5151
seeds?: TestCase[]; // 既存テストケース。
@@ -146,12 +146,12 @@ generate({
146146
```typescript
147147
interface GenerateResult {
148148
tests: TestCase[]; // 正常テストケース(無効値なし)。
149-
negativeTests?: TestCase[]; // ネガティブテスト(無効値が正確に1つ)。
149+
negativeTests: TestCase[]; // ネガティブテスト(無効値が正確に1つ)。該当なしの場合は空配列
150150
coverage: number; // カバレッジ比率(0.0 – 1.0)。
151151
uncovered: UncoveredTuple[]; // 未カバータプルと理由。
152152
stats: GenerateStats;
153153
suggestions: Suggestion[]; // 改善の提案。
154-
warnings: string[]; // パフォーマンス/設定の警告
154+
warnings: string[]; // 警告(例:カバレッジ100%未達、シード数がmaxTestsを超過)
155155
strength: number; // 使用された強度。
156156
classCoverage?: ClassCoverage; // 同値クラス定義時に存在。
157157
}
@@ -288,6 +288,17 @@ const result = cw.generate({
288288

289289
詳細は[制約構文](constraints.md)を参照してください。
290290

291+
## 入力バリデーション
292+
293+
Pure TypeScript API は入力を検証し、不正な値に対して説明的なエラーをスローします:
294+
295+
- `strength`: 正の整数である必要があります。非整数、負、ゼロは拒否されます。
296+
- `seed`: 有限な数値である必要があります。`NaN``Infinity` は拒否されます。
297+
- `maxTests`: 指定する場合は非負の整数である必要があります。
298+
- `parameters`: 空でない配列である必要があります。
299+
300+
WASM APIC++ 側の境界で同等のバリデーションを行います。
301+
291302
## エラーハンドリング
292303

293304
無効な入力の場合、関数は `CoverwiseError` をスローします:

js/compat.test.ts

Lines changed: 27 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -21,114 +21,37 @@ import {
2121
extend as tsExtendRaw,
2222
generate as tsGenerateRaw,
2323
} from '../src/ts/core/generator.js';
24-
import {
25-
createGenerateOptions,
26-
createWeightConfig,
27-
type GenerateOptions,
28-
type SubModel as InternalSubModel,
29-
type WeightConfig as InternalWeightConfig,
30-
} from '../src/ts/model/generate-options.js';
31-
import { Parameter as InternalParameter } from '../src/ts/model/parameter.js';
24+
import type { GenerateOptions } from '../src/ts/model/generate-options.js';
25+
import type { Parameter as InternalParameter } from '../src/ts/model/parameter.js';
3226
import type { TestCase as InternalTestCase } from '../src/ts/model/test-case.js';
3327
import { validateCoverage } from '../src/ts/validator/coverage-validator.js';
3428

35-
// ---------------------------------------------------------------------------
36-
// Adapter helpers: convert between WASM JSON format and TS internal format
37-
// ---------------------------------------------------------------------------
38-
39-
interface ParameterValueObj {
40-
value: string | number | boolean;
41-
invalid?: boolean;
42-
aliases?: string[];
43-
}
29+
// --- Adapter imports (shared conversion logic) ---
4430

45-
function toStringValue(v: string | number | boolean): string {
46-
return String(v);
47-
}
48-
49-
function convertParameters(wasmParams: Parameter[]): InternalParameter[] {
50-
return wasmParams.map((wp) => {
51-
const values: string[] = [];
52-
const invalid: boolean[] = [];
53-
const aliases: string[][] = [];
54-
let hasInvalid = false;
55-
let hasAliases = false;
56-
57-
for (const v of wp.values) {
58-
if (typeof v === 'object' && v !== null && 'value' in v) {
59-
const pv = v as ParameterValueObj;
60-
values.push(toStringValue(pv.value));
61-
invalid.push(pv.invalid === true);
62-
aliases.push(pv.aliases ?? []);
63-
if (pv.invalid) {
64-
hasInvalid = true;
65-
}
66-
if (pv.aliases && pv.aliases.length > 0) {
67-
hasAliases = true;
68-
}
69-
} else {
70-
values.push(toStringValue(v as string | number | boolean));
71-
invalid.push(false);
72-
aliases.push([]);
73-
}
74-
}
31+
import {
32+
toInternalOptions,
33+
toInternalParams,
34+
toInternalTestCase,
35+
toPublicTestCase,
36+
} from './pure/adapter.js';
7537

76-
const param = hasInvalid
77-
? new InternalParameter(wp.name, values, invalid)
78-
: new InternalParameter(wp.name, values);
79-
if (hasAliases) {
80-
param.setAliases(aliases);
81-
}
82-
return param;
83-
});
84-
}
38+
// ---------------------------------------------------------------------------
39+
// Thin wrappers around adapter functions for compat-test convenience
40+
// ---------------------------------------------------------------------------
8541

42+
/** Convert public TestCase to internal, delegating to adapter. */
8643
function namedTestToInternal(namedTest: TestCase, params: InternalParameter[]): InternalTestCase {
87-
const values: number[] = new Array(params.length);
88-
for (let i = 0; i < params.length; ++i) {
89-
const rawVal = namedTest[params[i].name];
90-
const strVal = toStringValue(rawVal);
91-
const idx = params[i].findValueIndex(strVal);
92-
values[i] = idx;
93-
}
94-
return { values };
44+
return toInternalTestCase(namedTest, params);
9545
}
9646

47+
/** Convert internal TestCase to public, using adapter with rotation=0. */
9748
function internalTestToNamed(tc: InternalTestCase, params: InternalParameter[]): TestCase {
98-
const result: TestCase = {};
99-
for (let i = 0; i < params.length; ++i) {
100-
result[params[i].name] = params[i].values[tc.values[i]];
101-
}
102-
return result;
49+
return toPublicTestCase(tc, params, 0);
10350
}
10451

52+
/** Build GenerateOptions from GenerateInput, delegating to adapter. */
10553
function buildGenerateOptions(input: GenerateInput, params: InternalParameter[]): GenerateOptions {
106-
const subModels: InternalSubModel[] = (input.subModels ?? []).map((sm) => ({
107-
parameterNames: sm.parameters,
108-
strength: sm.strength,
109-
}));
110-
111-
let weights: InternalWeightConfig = createWeightConfig();
112-
if (input.weights) {
113-
weights = { entries: { ...input.weights } };
114-
}
115-
116-
const seeds: InternalTestCase[] = (input.seeds ?? []).map((s) => namedTestToInternal(s, params));
117-
118-
return createGenerateOptions({
119-
parameters: params.map((p) => ({
120-
name: p.name,
121-
values: p.values,
122-
...(p.hasInvalidValues ? { invalid: p.invalid } : {}),
123-
})),
124-
constraintExpressions: input.constraints ?? [],
125-
strength: input.strength ?? 2,
126-
seed: input.seed ?? 0,
127-
maxTests: input.maxTests ?? 0,
128-
seeds,
129-
subModels,
130-
weights,
131-
});
54+
return toInternalOptions(input, params);
13255
}
13356

13457
// ---------------------------------------------------------------------------
@@ -148,7 +71,7 @@ function tsGenerate(input: GenerateInput): GenerateResult {
14871
strength: input.strength ?? 2,
14972
};
15073
}
151-
const params = convertParameters(input.parameters);
74+
const params = toInternalParams(input.parameters);
15275
const opts = buildGenerateOptions(input, params);
15376
const result = tsGenerateRaw(opts);
15477

@@ -179,7 +102,7 @@ function tsAnalyzeCoverage(
179102
tests: TestCase[],
180103
strength?: number,
181104
): CoverageReport {
182-
const params = convertParameters(parameters);
105+
const params = toInternalParams(parameters);
183106
const internalTests: InternalTestCase[] = tests.map((t) => namedTestToInternal(t, params));
184107
const report = validateCoverage(params, internalTests, strength ?? 2);
185108
// Match WASM behavior: 0 tuples => coverageRatio 1.0
@@ -190,7 +113,7 @@ function tsAnalyzeCoverage(
190113
}
191114

192115
function tsExtendTests(existing: TestCase[], input: GenerateInput): GenerateResult {
193-
const params = convertParameters(input.parameters);
116+
const params = toInternalParams(input.parameters);
194117
const existingInternal = existing.map((t) => namedTestToInternal(t, params));
195118
const opts = buildGenerateOptions(input, params);
196119
const result = tsExtendRaw(existingInternal, opts);
@@ -211,7 +134,7 @@ function tsExtendTests(existing: TestCase[], input: GenerateInput): GenerateResu
211134
}
212135

213136
function tsEstimateModel(input: GenerateInput): ModelStats {
214-
const params = convertParameters(input.parameters);
137+
const params = toInternalParams(input.parameters);
215138
const opts = buildGenerateOptions(input, params);
216139
return tsEstimateModelRaw(opts);
217140
}
@@ -405,11 +328,11 @@ describe('WASM / TS compatibility', () => {
405328
});
406329
}
407330

408-
// Exact output match tests are skipped because the C++ and TypeScript
409-
// engines use different RNG implementations (both xoshiro128** but with
410-
// different internal sequencing due to algorithm-level differences in
411-
// candidate generation and value iteration order). Both produce correct
412-
// covering arrays but with different test orderings and counts.
331+
// Exact output match tests are skipped because the C++ / WASM engine uses
332+
// std::mt19937_64 (Mersenne Twister 64-bit) while the TypeScript engine
333+
// uses xoshiro128**. The same seed produces different sequences across
334+
// engines, but both are deterministic within their own engine.
335+
// Compatibility tests compare coverage completeness, not exact output.
413336
for (const { name, input } of scenarios) {
414337
it.skip(`${name}: exact test output match`, () => {
415338
const wasmResult = generate(input);

0 commit comments

Comments
 (0)