@paths.design/caws-cli 2.0.0 โ 3.0.0
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.
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +101 -96
- package/package.json +3 -3
- package/templates/agents.md +820 -0
- package/templates/apps/tools/caws/COMPLETION_REPORT.md +331 -0
- package/templates/apps/tools/caws/MIGRATION_SUMMARY.md +360 -0
- package/templates/apps/tools/caws/README.md +463 -0
- package/templates/apps/tools/caws/TEST_STATUS.md +365 -0
- package/templates/apps/tools/caws/attest.js +357 -0
- package/templates/apps/tools/caws/ci-optimizer.js +642 -0
- package/templates/apps/tools/caws/config.ts +245 -0
- package/templates/apps/tools/caws/cross-functional.js +876 -0
- package/templates/apps/tools/caws/dashboard.js +1112 -0
- package/templates/apps/tools/caws/flake-detector.ts +362 -0
- package/templates/apps/tools/caws/gates.js +198 -0
- package/templates/apps/tools/caws/gates.ts +237 -0
- package/templates/apps/tools/caws/language-adapters.ts +381 -0
- package/templates/apps/tools/caws/language-support.d.ts +367 -0
- package/templates/apps/tools/caws/language-support.d.ts.map +1 -0
- package/templates/apps/tools/caws/language-support.js +585 -0
- package/templates/apps/tools/caws/legacy-assessment.ts +408 -0
- package/templates/apps/tools/caws/legacy-assessor.js +764 -0
- package/templates/apps/tools/caws/mutant-analyzer.js +734 -0
- package/templates/apps/tools/caws/perf-budgets.ts +349 -0
- package/templates/apps/tools/caws/property-testing.js +707 -0
- package/templates/apps/tools/caws/provenance.d.ts +14 -0
- package/templates/apps/tools/caws/provenance.d.ts.map +1 -0
- package/templates/apps/tools/caws/provenance.js +132 -0
- package/templates/apps/tools/caws/provenance.ts +211 -0
- package/templates/apps/tools/caws/schemas/waivers.schema.json +30 -0
- package/templates/apps/tools/caws/schemas/working-spec.schema.json +115 -0
- package/templates/apps/tools/caws/scope-guard.js +208 -0
- package/templates/apps/tools/caws/security-provenance.ts +483 -0
- package/templates/apps/tools/caws/shared/base-tool.ts +281 -0
- package/templates/apps/tools/caws/shared/config-manager.ts +366 -0
- package/templates/apps/tools/caws/shared/gate-checker.ts +597 -0
- package/templates/apps/tools/caws/shared/types.ts +444 -0
- package/templates/apps/tools/caws/shared/validator.ts +305 -0
- package/templates/apps/tools/caws/shared/waivers-manager.ts +174 -0
- package/templates/apps/tools/caws/spec-test-mapper.ts +391 -0
- package/templates/apps/tools/caws/templates/working-spec.template.yml +60 -0
- package/templates/apps/tools/caws/test-quality.js +578 -0
- package/templates/apps/tools/caws/tools-allow.json +331 -0
- package/templates/apps/tools/caws/validate.js +76 -0
- package/templates/apps/tools/caws/validate.ts +228 -0
- package/templates/apps/tools/caws/waivers.js +344 -0
- package/templates/apps/tools/caws/waivers.yml +19 -0
- package/templates/codemod/README.md +1 -0
- package/templates/codemod/test.js +1 -0
- package/templates/docs/README.md +150 -0
|
@@ -0,0 +1,707 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview CAWS Property-Based Testing Integration
|
|
5
|
+
* Generates and runs property-based tests for enhanced test coverage
|
|
6
|
+
* @author @darianrosebrook
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const { execSync } = require('child_process');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Property-based testing configurations for different languages
|
|
15
|
+
*/
|
|
16
|
+
const PROPERTY_TESTING_CONFIGS = {
|
|
17
|
+
javascript: {
|
|
18
|
+
library: 'fast-check',
|
|
19
|
+
setup: {
|
|
20
|
+
install: 'npm install --save-dev fast-check',
|
|
21
|
+
import: "import fc from 'fast-check';",
|
|
22
|
+
runner: 'npm test',
|
|
23
|
+
},
|
|
24
|
+
templates: {
|
|
25
|
+
number: (propertyName) => `
|
|
26
|
+
describe('${propertyName}', () => {
|
|
27
|
+
test('should satisfy ${propertyName}', () => {
|
|
28
|
+
fc.assert(
|
|
29
|
+
fc.property(
|
|
30
|
+
fc.integer(),
|
|
31
|
+
fc.string(),
|
|
32
|
+
(a, b) => {
|
|
33
|
+
// Property: ${propertyName}
|
|
34
|
+
// Implement your property here
|
|
35
|
+
return true; // Replace with actual property
|
|
36
|
+
}
|
|
37
|
+
)
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
});`,
|
|
41
|
+
|
|
42
|
+
array: (propertyName) => `
|
|
43
|
+
describe('${propertyName}', () => {
|
|
44
|
+
test('should satisfy ${propertyName}', () => {
|
|
45
|
+
fc.assert(
|
|
46
|
+
fc.property(
|
|
47
|
+
fc.array(fc.integer()),
|
|
48
|
+
(arr) => {
|
|
49
|
+
// Property: ${propertyName}
|
|
50
|
+
// Implement your property here
|
|
51
|
+
return true; // Replace with actual property
|
|
52
|
+
}
|
|
53
|
+
)
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
});`,
|
|
57
|
+
|
|
58
|
+
object: (propertyName) => `
|
|
59
|
+
describe('${propertyName}', () => {
|
|
60
|
+
test('should satisfy ${propertyName}', () => {
|
|
61
|
+
fc.assert(
|
|
62
|
+
fc.property(
|
|
63
|
+
fc.record({
|
|
64
|
+
id: fc.integer(),
|
|
65
|
+
name: fc.string(),
|
|
66
|
+
active: fc.boolean()
|
|
67
|
+
}),
|
|
68
|
+
(obj) => {
|
|
69
|
+
// Property: ${propertyName}
|
|
70
|
+
// Implement your property here
|
|
71
|
+
return true; // Replace with actual property
|
|
72
|
+
}
|
|
73
|
+
)
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
});`,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
python: {
|
|
81
|
+
library: 'hypothesis',
|
|
82
|
+
setup: {
|
|
83
|
+
install: 'pip install hypothesis',
|
|
84
|
+
import: 'from hypothesis import given, strategies as st',
|
|
85
|
+
runner: 'pytest',
|
|
86
|
+
},
|
|
87
|
+
templates: {
|
|
88
|
+
number: (propertyName) => `
|
|
89
|
+
@given(st.integers(), st.text())
|
|
90
|
+
def test_${propertyName.replace(/\s+/g, '_').toLowerCase()}(a, b):
|
|
91
|
+
# Property: ${propertyName}
|
|
92
|
+
# Implement your property here
|
|
93
|
+
assert True # Replace with actual property`,
|
|
94
|
+
|
|
95
|
+
array: (propertyName) => `
|
|
96
|
+
@given(st.lists(st.integers()))
|
|
97
|
+
def test_${propertyName.replace(/\s+/g, '_').toLowerCase()}(arr):
|
|
98
|
+
# Property: ${propertyName}
|
|
99
|
+
# Implement your property here
|
|
100
|
+
assert True # Replace with actual property`,
|
|
101
|
+
|
|
102
|
+
object: (propertyName) => `
|
|
103
|
+
@given(st.fixed_dictionaries({
|
|
104
|
+
'id': st.integers(),
|
|
105
|
+
'name': st.text(),
|
|
106
|
+
'active': st.booleans()
|
|
107
|
+
}))
|
|
108
|
+
def test_${propertyName.replace(/\s+/g, '_').toLowerCase()}(obj):
|
|
109
|
+
# Property: ${propertyName}
|
|
110
|
+
# Implement your property here
|
|
111
|
+
assert True # Replace with actual property`,
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
java: {
|
|
116
|
+
library: 'jqwik',
|
|
117
|
+
setup: {
|
|
118
|
+
install: 'implementation "net.jqwik:jqwik:1.7.4"',
|
|
119
|
+
import: 'import net.jqwik.api.*;',
|
|
120
|
+
runner: './gradlew test',
|
|
121
|
+
},
|
|
122
|
+
templates: {
|
|
123
|
+
number: (propertyName) => `
|
|
124
|
+
@Property
|
|
125
|
+
boolean ${propertyName.replace(/\s+/g, '_').toLowerCase()}(
|
|
126
|
+
@ForAll int a,
|
|
127
|
+
@ForAll String b) {
|
|
128
|
+
// Property: ${propertyName}
|
|
129
|
+
// Implement your property here
|
|
130
|
+
return true; // Replace with actual property
|
|
131
|
+
}`,
|
|
132
|
+
|
|
133
|
+
array: (propertyName) => `
|
|
134
|
+
@Property
|
|
135
|
+
boolean ${propertyName.replace(/\s+/g, '_').toLowerCase()}(
|
|
136
|
+
@ForAll List<Integer> arr) {
|
|
137
|
+
// Property: ${propertyName}
|
|
138
|
+
// Implement your property here
|
|
139
|
+
return true; // Replace with actual property
|
|
140
|
+
}`,
|
|
141
|
+
|
|
142
|
+
object: (propertyName) => `
|
|
143
|
+
@Property
|
|
144
|
+
boolean ${propertyName.replace(/\s+/g, '_').toLowerCase()}(
|
|
145
|
+
@ForAll("person") Person person) {
|
|
146
|
+
// Property: ${propertyName}
|
|
147
|
+
// Implement your property here
|
|
148
|
+
return true; // Replace with actual property
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@Provide
|
|
152
|
+
Arbitrary<Person> person() {
|
|
153
|
+
return Combinators.combine(Person::new)
|
|
154
|
+
.with(Arbitraries.integers())
|
|
155
|
+
.with(Arbitraries.strings())
|
|
156
|
+
.with(Arbitraries.of(true, false));
|
|
157
|
+
}`,
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Common property types that should be tested
|
|
164
|
+
*/
|
|
165
|
+
const COMMON_PROPERTIES = {
|
|
166
|
+
idempotent: {
|
|
167
|
+
name: 'Idempotent operations',
|
|
168
|
+
description:
|
|
169
|
+
'Running the same operation multiple times should have the same effect as running it once',
|
|
170
|
+
examples: ['sorting algorithms', 'normalization functions', 'cleanup operations'],
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
commutative: {
|
|
174
|
+
name: 'Commutative operations',
|
|
175
|
+
description: 'Order of operations should not matter',
|
|
176
|
+
examples: ['addition', 'set operations', 'string concatenation'],
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
associative: {
|
|
180
|
+
name: 'Associative operations',
|
|
181
|
+
description: 'Grouping of operations should not matter',
|
|
182
|
+
examples: ['addition', 'multiplication', 'function composition'],
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
inverse: {
|
|
186
|
+
name: 'Inverse operations',
|
|
187
|
+
description: 'Operations should have meaningful inverses',
|
|
188
|
+
examples: ['encode/decode', 'encrypt/decrypt', 'parse/format'],
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
monotonic: {
|
|
192
|
+
name: 'Monotonic functions',
|
|
193
|
+
description: 'Functions should preserve or reverse order',
|
|
194
|
+
examples: ['sorting functions', 'comparison operations'],
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
roundtrip: {
|
|
198
|
+
name: 'Roundtrip consistency',
|
|
199
|
+
description: 'Convert to another format and back should preserve original',
|
|
200
|
+
examples: ['serialization/deserialization', 'encoding/decoding'],
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
error_handling: {
|
|
204
|
+
name: 'Error handling',
|
|
205
|
+
description: 'Invalid inputs should be handled gracefully',
|
|
206
|
+
examples: ['null/undefined checks', 'boundary conditions', 'invalid formats'],
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Generate property-based tests for a given language and properties
|
|
212
|
+
* @param {string} language - Target language (javascript, python, java)
|
|
213
|
+
* @param {Array} properties - List of property names to generate tests for
|
|
214
|
+
* @param {string} outputDir - Output directory for test files
|
|
215
|
+
*/
|
|
216
|
+
function generatePropertyTests(language, properties, outputDir = 'tests/property') {
|
|
217
|
+
const config = PROPERTY_TESTING_CONFIGS[language];
|
|
218
|
+
if (!config) {
|
|
219
|
+
throw new Error(`Unsupported language: ${language}`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
console.log(`๐ง Generating property-based tests for ${language}...`);
|
|
223
|
+
|
|
224
|
+
// Ensure output directory exists
|
|
225
|
+
if (!fs.existsSync(outputDir)) {
|
|
226
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Generate setup file
|
|
230
|
+
generateSetupFile(language, outputDir);
|
|
231
|
+
|
|
232
|
+
// Generate tests for each property type
|
|
233
|
+
properties.forEach((propertyName) => {
|
|
234
|
+
const property = COMMON_PROPERTIES[propertyName];
|
|
235
|
+
if (!property) {
|
|
236
|
+
console.warn(`โ ๏ธ Unknown property: ${propertyName}`);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const templateKey = inferTemplateType(property);
|
|
241
|
+
const testContent = config.templates[templateKey](property.name);
|
|
242
|
+
|
|
243
|
+
const fileName = `${propertyName}.test.${getFileExtension(language)}`;
|
|
244
|
+
const filePath = path.join(outputDir, fileName);
|
|
245
|
+
|
|
246
|
+
// Add property description as comments
|
|
247
|
+
const enhancedContent = `/**
|
|
248
|
+
* Property-based test: ${property.name}
|
|
249
|
+
* Description: ${property.description}
|
|
250
|
+
* Examples: ${property.examples.join(', ')}
|
|
251
|
+
*/
|
|
252
|
+
|
|
253
|
+
${testContent}`;
|
|
254
|
+
|
|
255
|
+
fs.writeFileSync(filePath, enhancedContent);
|
|
256
|
+
console.log(`โ
Generated ${propertyName} test: ${filePath}`);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Generate README
|
|
260
|
+
generatePropertyTestingReadme(language, properties, outputDir);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Infer template type from property description
|
|
265
|
+
*/
|
|
266
|
+
function inferTemplateType(property) {
|
|
267
|
+
if (property.examples.some((ex) => ex.includes('array') || ex.includes('list'))) {
|
|
268
|
+
return 'array';
|
|
269
|
+
}
|
|
270
|
+
if (property.examples.some((ex) => ex.includes('object') || ex.includes('record'))) {
|
|
271
|
+
return 'object';
|
|
272
|
+
}
|
|
273
|
+
return 'number'; // Default
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Get file extension for language
|
|
278
|
+
*/
|
|
279
|
+
function getFileExtension(language) {
|
|
280
|
+
const extensions = {
|
|
281
|
+
javascript: 'js',
|
|
282
|
+
python: 'py',
|
|
283
|
+
java: 'java',
|
|
284
|
+
};
|
|
285
|
+
return extensions[language] || 'js';
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Generate setup file for property-based testing
|
|
290
|
+
*/
|
|
291
|
+
function generateSetupFile(language, outputDir) {
|
|
292
|
+
const config = PROPERTY_TESTING_CONFIGS[language];
|
|
293
|
+
|
|
294
|
+
let setupContent = '';
|
|
295
|
+
|
|
296
|
+
switch (language) {
|
|
297
|
+
case 'javascript':
|
|
298
|
+
setupContent = `// Property-based testing setup for JavaScript
|
|
299
|
+
// Run: ${config.setup.install}
|
|
300
|
+
|
|
301
|
+
${config.setup.import}
|
|
302
|
+
|
|
303
|
+
// Configure fast-check for better shrinking and debugging
|
|
304
|
+
fc.configureGlobal({
|
|
305
|
+
verbose: true,
|
|
306
|
+
seed: 42,
|
|
307
|
+
numRuns: 100 // Increase for production
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
module.exports = { fc };
|
|
311
|
+
`;
|
|
312
|
+
break;
|
|
313
|
+
|
|
314
|
+
case 'python':
|
|
315
|
+
setupContent = `# Property-based testing setup for Python
|
|
316
|
+
# Run: ${config.setup.install}
|
|
317
|
+
|
|
318
|
+
${config.setup.import}
|
|
319
|
+
|
|
320
|
+
# Configure hypothesis for better test runs
|
|
321
|
+
settings.register_profile("ci", settings(max_examples=1000))
|
|
322
|
+
settings.load_profile("ci")
|
|
323
|
+
`;
|
|
324
|
+
break;
|
|
325
|
+
|
|
326
|
+
case 'java':
|
|
327
|
+
setupContent = `// Property-based testing setup for Java
|
|
328
|
+
// Add to build.gradle: ${config.setup.install}
|
|
329
|
+
|
|
330
|
+
${config.setup.import}
|
|
331
|
+
|
|
332
|
+
// Configure jqwik for better test runs
|
|
333
|
+
@Configure
|
|
334
|
+
class JqwikConfiguration {
|
|
335
|
+
@Provide
|
|
336
|
+
Arbitrary<Integer> integers() {
|
|
337
|
+
return Arbitraries.integers().between(-1000, 1000);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
@Provide
|
|
341
|
+
Arbitrary<String> strings() {
|
|
342
|
+
return Arbitraries.strings().withLength(0, 50);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
`;
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const setupPath = path.join(outputDir, `setup.${getFileExtension(language)}`);
|
|
350
|
+
fs.writeFileSync(setupPath, setupContent);
|
|
351
|
+
console.log(`โ
Generated setup file: ${setupPath}`);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Generate README for property-based testing
|
|
356
|
+
*/
|
|
357
|
+
function generatePropertyTestingReadme(language, properties, outputDir) {
|
|
358
|
+
const config = PROPERTY_TESTING_CONFIGS[language];
|
|
359
|
+
|
|
360
|
+
const readmeContent = `# Property-Based Testing
|
|
361
|
+
|
|
362
|
+
This directory contains property-based tests generated by CAWS.
|
|
363
|
+
|
|
364
|
+
## Setup
|
|
365
|
+
|
|
366
|
+
1. Install dependencies:
|
|
367
|
+
\`\`\`bash
|
|
368
|
+
${config.setup.install}
|
|
369
|
+
\`\`\`
|
|
370
|
+
|
|
371
|
+
2. Run property tests:
|
|
372
|
+
\`\`\`bash
|
|
373
|
+
${config.setup.runner}
|
|
374
|
+
\`\`\`
|
|
375
|
+
|
|
376
|
+
## Generated Properties
|
|
377
|
+
|
|
378
|
+
${properties.map((prop) => `- **${COMMON_PROPERTIES[prop].name}**: ${COMMON_PROPERTIES[prop].description}`).join('\n')}
|
|
379
|
+
|
|
380
|
+
## Understanding Properties
|
|
381
|
+
|
|
382
|
+
### Idempotent Operations
|
|
383
|
+
An operation is idempotent if running it multiple times has the same effect as running it once.
|
|
384
|
+
|
|
385
|
+
### Commutative Operations
|
|
386
|
+
Order doesn't matter - f(a, b) = f(b, a)
|
|
387
|
+
|
|
388
|
+
### Associative Operations
|
|
389
|
+
Grouping doesn't matter - f(f(a, b), c) = f(a, f(b, c))
|
|
390
|
+
|
|
391
|
+
### Inverse Operations
|
|
392
|
+
Operations should have meaningful inverses that restore original state
|
|
393
|
+
|
|
394
|
+
## Tips for Writing Properties
|
|
395
|
+
|
|
396
|
+
1. **Start Simple**: Begin with obvious properties, then add more complex ones
|
|
397
|
+
2. **Use Generators**: Create appropriate input generators for your domain
|
|
398
|
+
3. **Handle Edge Cases**: Ensure properties work with null, empty, and boundary values
|
|
399
|
+
4. **Document Assumptions**: Clearly state what your property assumes about inputs
|
|
400
|
+
|
|
401
|
+
## Examples
|
|
402
|
+
|
|
403
|
+
\`\`\`${language}
|
|
404
|
+
${config.templates.number('Example property')}
|
|
405
|
+
\`\`\`
|
|
406
|
+
`;
|
|
407
|
+
|
|
408
|
+
fs.writeFileSync(path.join(outputDir, 'README.md'), readmeContent);
|
|
409
|
+
console.log(`โ
Generated README: ${path.join(outputDir, 'README.md')}`);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Run property-based tests and analyze results
|
|
414
|
+
* @param {string} language - Target language
|
|
415
|
+
* @param {string} testDir - Test directory
|
|
416
|
+
* @returns {Object} Test results
|
|
417
|
+
*/
|
|
418
|
+
function runPropertyTests(language, testDir = 'tests/property') {
|
|
419
|
+
const config = PROPERTY_TESTING_CONFIGS[language];
|
|
420
|
+
if (!config) {
|
|
421
|
+
throw new Error(`Unsupported language: ${language}`);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
console.log(`๐งช Running property-based tests for ${language}...`);
|
|
425
|
+
|
|
426
|
+
const results = {
|
|
427
|
+
total: 0,
|
|
428
|
+
passed: 0,
|
|
429
|
+
failed: 0,
|
|
430
|
+
errors: [],
|
|
431
|
+
coverage: {},
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
try {
|
|
435
|
+
// Check if test files exist
|
|
436
|
+
if (!fs.existsSync(testDir)) {
|
|
437
|
+
results.errors.push(`Test directory not found: ${testDir}`);
|
|
438
|
+
return results;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const testFiles = fs
|
|
442
|
+
.readdirSync(testDir)
|
|
443
|
+
.filter(
|
|
444
|
+
(file) =>
|
|
445
|
+
file.endsWith(`.test.${getFileExtension(language)}`) ||
|
|
446
|
+
file.endsWith(`_test.${getFileExtension(language)}`)
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
if (testFiles.length === 0) {
|
|
450
|
+
results.errors.push(`No property test files found in ${testDir}`);
|
|
451
|
+
return results;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Run tests based on language
|
|
455
|
+
let testOutput;
|
|
456
|
+
try {
|
|
457
|
+
switch (language) {
|
|
458
|
+
case 'javascript':
|
|
459
|
+
testOutput = execSync(`${config.setup.runner} -- --testPathPattern=property`, {
|
|
460
|
+
encoding: 'utf8',
|
|
461
|
+
cwd: process.cwd(),
|
|
462
|
+
});
|
|
463
|
+
break;
|
|
464
|
+
case 'python':
|
|
465
|
+
testOutput = execSync(`${config.setup.runner} ${testDir}`, {
|
|
466
|
+
encoding: 'utf8',
|
|
467
|
+
cwd: process.cwd(),
|
|
468
|
+
});
|
|
469
|
+
break;
|
|
470
|
+
case 'java':
|
|
471
|
+
testOutput = execSync(config.setup.runner, {
|
|
472
|
+
encoding: 'utf8',
|
|
473
|
+
cwd: process.cwd(),
|
|
474
|
+
});
|
|
475
|
+
break;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Parse test output
|
|
479
|
+
results.total = (testOutput.match(/test|spec/g) || []).length;
|
|
480
|
+
results.passed = (testOutput.match(/โ|passed|ok/g) || []).length;
|
|
481
|
+
results.failed = (testOutput.match(/โ|failed|error/g) || []).length;
|
|
482
|
+
} catch (error) {
|
|
483
|
+
results.errors.push(`Test execution failed: ${error.message}`);
|
|
484
|
+
results.failed = testFiles.length; // Assume all failed if execution failed
|
|
485
|
+
}
|
|
486
|
+
} catch (error) {
|
|
487
|
+
results.errors.push(`Error running property tests: ${error.message}`);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return results;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Analyze property testing coverage and suggest improvements
|
|
495
|
+
* @param {Object} testResults - Results from runPropertyTests
|
|
496
|
+
* @param {Array} implementedProperties - List of implemented properties
|
|
497
|
+
* @returns {Object} Coverage analysis
|
|
498
|
+
*/
|
|
499
|
+
function analyzePropertyCoverage(testResults, implementedProperties) {
|
|
500
|
+
const analysis = {
|
|
501
|
+
coverage_score: 0,
|
|
502
|
+
missing_properties: [],
|
|
503
|
+
recommendations: [],
|
|
504
|
+
strengths: [],
|
|
505
|
+
weaknesses: [],
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
if (testResults.total === 0) {
|
|
509
|
+
analysis.missing_properties = Object.keys(COMMON_PROPERTIES);
|
|
510
|
+
analysis.recommendations.push('No property tests implemented');
|
|
511
|
+
return analysis;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Calculate coverage score
|
|
515
|
+
const implementedSet = new Set(implementedProperties);
|
|
516
|
+
const totalProperties = Object.keys(COMMON_PROPERTIES).length;
|
|
517
|
+
analysis.coverage_score = (implementedSet.size / totalProperties) * 100;
|
|
518
|
+
|
|
519
|
+
// Find missing properties
|
|
520
|
+
Object.keys(COMMON_PROPERTIES).forEach((prop) => {
|
|
521
|
+
if (!implementedSet.has(prop)) {
|
|
522
|
+
analysis.missing_properties.push(prop);
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
// Generate recommendations
|
|
527
|
+
if (analysis.missing_properties.length > 0) {
|
|
528
|
+
analysis.recommendations.push(
|
|
529
|
+
`Missing ${analysis.missing_properties.length} property types: ${analysis.missing_properties.join(', ')}`
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (testResults.failed > 0) {
|
|
534
|
+
analysis.weaknesses.push(`${testResults.failed} property tests are failing`);
|
|
535
|
+
analysis.recommendations.push('Fix failing property tests and strengthen property definitions');
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (testResults.passed > 0) {
|
|
539
|
+
analysis.strengths.push(`${testResults.passed} property tests are passing`);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (analysis.coverage_score >= 80) {
|
|
543
|
+
analysis.strengths.push('Good property coverage');
|
|
544
|
+
} else if (analysis.coverage_score >= 50) {
|
|
545
|
+
analysis.recommendations.push('Consider adding more property types for better coverage');
|
|
546
|
+
} else {
|
|
547
|
+
analysis.recommendations.push(
|
|
548
|
+
'Property testing coverage is low - prioritize adding key properties'
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
return analysis;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// CLI interface
|
|
556
|
+
if (require.main === module) {
|
|
557
|
+
const command = process.argv[2];
|
|
558
|
+
|
|
559
|
+
switch (command) {
|
|
560
|
+
case 'generate':
|
|
561
|
+
const language = process.argv[3] || 'javascript';
|
|
562
|
+
const properties = process.argv[4]
|
|
563
|
+
? process.argv[4].split(',')
|
|
564
|
+
: ['idempotent', 'commutative'];
|
|
565
|
+
const outputDir = process.argv[5] || 'tests/property';
|
|
566
|
+
|
|
567
|
+
if (!PROPERTY_TESTING_CONFIGS[language]) {
|
|
568
|
+
console.error(`โ Unsupported language: ${language}`);
|
|
569
|
+
console.error(`Supported languages: ${Object.keys(PROPERTY_TESTING_CONFIGS).join(', ')}`);
|
|
570
|
+
process.exit(1);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
try {
|
|
574
|
+
generatePropertyTests(language, properties, outputDir);
|
|
575
|
+
console.log(`\n๐ฏ Generated property-based tests for ${language}`);
|
|
576
|
+
console.log(`Properties: ${properties.join(', ')}`);
|
|
577
|
+
console.log(`Output directory: ${outputDir}`);
|
|
578
|
+
} catch (error) {
|
|
579
|
+
console.error(`โ Error generating property tests: ${error.message}`);
|
|
580
|
+
process.exit(1);
|
|
581
|
+
}
|
|
582
|
+
break;
|
|
583
|
+
|
|
584
|
+
case 'run':
|
|
585
|
+
const runLanguage = process.argv[3] || 'javascript';
|
|
586
|
+
const testDir = process.argv[4] || 'tests/property';
|
|
587
|
+
|
|
588
|
+
try {
|
|
589
|
+
const results = runPropertyTests(runLanguage, testDir);
|
|
590
|
+
|
|
591
|
+
console.log('\n๐งช Property Test Results:');
|
|
592
|
+
console.log(` Total: ${results.total}`);
|
|
593
|
+
console.log(` Passed: ${results.passed}`);
|
|
594
|
+
console.log(` Failed: ${results.failed}`);
|
|
595
|
+
|
|
596
|
+
if (results.errors.length > 0) {
|
|
597
|
+
console.log('\nโ Errors:');
|
|
598
|
+
results.errors.forEach((error) => console.log(` - ${error}`));
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if (results.failed > 0) {
|
|
602
|
+
console.error(`\nโ ${results.failed} property tests failed`);
|
|
603
|
+
process.exit(1);
|
|
604
|
+
} else if (results.passed === 0) {
|
|
605
|
+
console.warn('โ ๏ธ No property tests passed');
|
|
606
|
+
process.exit(1);
|
|
607
|
+
} else {
|
|
608
|
+
console.log('โ
All property tests passed');
|
|
609
|
+
}
|
|
610
|
+
} catch (error) {
|
|
611
|
+
console.error(`โ Error running property tests: ${error.message}`);
|
|
612
|
+
process.exit(1);
|
|
613
|
+
}
|
|
614
|
+
break;
|
|
615
|
+
|
|
616
|
+
case 'analyze':
|
|
617
|
+
const analyzeLanguage = process.argv[3] || 'javascript';
|
|
618
|
+
const testResultsArg = process.argv[4] || 'tests/property';
|
|
619
|
+
const implementedProps = process.argv[5] ? process.argv[5].split(',') : [];
|
|
620
|
+
|
|
621
|
+
try {
|
|
622
|
+
// Run tests first if not provided
|
|
623
|
+
let results;
|
|
624
|
+
if (typeof testResultsArg === 'string' && fs.existsSync(testResultsArg)) {
|
|
625
|
+
results = runPropertyTests(analyzeLanguage, testResultsArg);
|
|
626
|
+
} else {
|
|
627
|
+
// Assume test results are passed as arguments
|
|
628
|
+
results = {
|
|
629
|
+
total: parseInt(process.argv[4]) || 0,
|
|
630
|
+
passed: parseInt(process.argv[5]) || 0,
|
|
631
|
+
failed: parseInt(process.argv[6]) || 0,
|
|
632
|
+
errors: [],
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const coverage = analyzePropertyCoverage(results, implementedProps);
|
|
637
|
+
|
|
638
|
+
console.log('\n๐ Property Testing Coverage Analysis:');
|
|
639
|
+
console.log(` Coverage Score: ${Math.round(coverage.coverage_score)}/100`);
|
|
640
|
+
|
|
641
|
+
if (coverage.strengths.length > 0) {
|
|
642
|
+
console.log('\nโ
Strengths:');
|
|
643
|
+
coverage.strengths.forEach((strength) => console.log(` - ${strength}`));
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
if (coverage.weaknesses.length > 0) {
|
|
647
|
+
console.log('\nโ ๏ธ Weaknesses:');
|
|
648
|
+
coverage.weaknesses.forEach((weakness) => console.log(` - ${weakness}`));
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
if (coverage.missing_properties.length > 0) {
|
|
652
|
+
console.log('\n๐ Missing Properties:');
|
|
653
|
+
coverage.missing_properties.forEach((prop) => {
|
|
654
|
+
const property = COMMON_PROPERTIES[prop];
|
|
655
|
+
console.log(` - ${property.name}: ${property.description}`);
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (coverage.recommendations.length > 0) {
|
|
660
|
+
console.log('\n๐ก Recommendations:');
|
|
661
|
+
coverage.recommendations.forEach((rec) => console.log(` - ${rec}`));
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
if (coverage.coverage_score < 70) {
|
|
665
|
+
console.error(
|
|
666
|
+
`\nโ Property testing coverage too low: ${Math.round(coverage.coverage_score)}/100`
|
|
667
|
+
);
|
|
668
|
+
process.exit(1);
|
|
669
|
+
}
|
|
670
|
+
} catch (error) {
|
|
671
|
+
console.error(`โ Error analyzing property coverage: ${error.message}`);
|
|
672
|
+
process.exit(1);
|
|
673
|
+
}
|
|
674
|
+
break;
|
|
675
|
+
|
|
676
|
+
default:
|
|
677
|
+
console.log('CAWS Property-Based Testing Tool');
|
|
678
|
+
console.log('Usage:');
|
|
679
|
+
console.log(' node property-testing.js generate <language> [properties] [output-dir]');
|
|
680
|
+
console.log(' node property-testing.js run <language> [test-dir]');
|
|
681
|
+
console.log(' node property-testing.js analyze <language> [test-dir] [properties]');
|
|
682
|
+
console.log('');
|
|
683
|
+
console.log('Supported languages:');
|
|
684
|
+
Object.keys(PROPERTY_TESTING_CONFIGS).forEach((lang) => {
|
|
685
|
+
console.log(` - ${lang}: ${PROPERTY_TESTING_CONFIGS[lang].library}`);
|
|
686
|
+
});
|
|
687
|
+
console.log('');
|
|
688
|
+
console.log('Available properties:');
|
|
689
|
+
Object.keys(COMMON_PROPERTIES).forEach((prop) => {
|
|
690
|
+
console.log(` - ${prop}: ${COMMON_PROPERTIES[prop].name}`);
|
|
691
|
+
});
|
|
692
|
+
console.log('');
|
|
693
|
+
console.log('Examples:');
|
|
694
|
+
console.log(' node property-testing.js generate javascript idempotent,commutative');
|
|
695
|
+
console.log(' node property-testing.js run python tests/property');
|
|
696
|
+
console.log(' node property-testing.js analyze java 5 3 2');
|
|
697
|
+
process.exit(1);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
module.exports = {
|
|
702
|
+
generatePropertyTests,
|
|
703
|
+
runPropertyTests,
|
|
704
|
+
analyzePropertyCoverage,
|
|
705
|
+
COMMON_PROPERTIES,
|
|
706
|
+
PROPERTY_TESTING_CONFIGS,
|
|
707
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Generate provenance manifest
|
|
4
|
+
* @param {Object} options - Provenance options
|
|
5
|
+
* @returns {Object} Provenance manifest
|
|
6
|
+
*/
|
|
7
|
+
export function generateProvenance(options?: any): any;
|
|
8
|
+
/**
|
|
9
|
+
* Save provenance manifest to file
|
|
10
|
+
* @param {Object} provenance - Provenance manifest
|
|
11
|
+
* @param {string} filepath - File path to save to
|
|
12
|
+
*/
|
|
13
|
+
export function saveProvenance(provenance: any, filepath: string): void;
|
|
14
|
+
//# sourceMappingURL=provenance.d.ts.map
|