js4j 0.1.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/README.md +684 -0
- package/examples/01-basic.js +75 -0
- package/examples/02-collections.js +108 -0
- package/examples/03-objects.js +99 -0
- package/examples/04-callbacks.js +97 -0
- package/examples/05-stdlib.js +123 -0
- package/examples/_gateway.js +78 -0
- package/index.d.ts +455 -0
- package/index.js +78 -0
- package/java/TestEntryPoint.java +262 -0
- package/java/py4j.jar +0 -0
- package/package.json +29 -0
- package/src/callbackServer.js +269 -0
- package/src/clientServer.js +81 -0
- package/src/collections.js +325 -0
- package/src/connection.js +253 -0
- package/src/exceptions.js +68 -0
- package/src/gateway.js +222 -0
- package/src/javaObject.js +267 -0
- package/src/jvmView.js +224 -0
- package/src/launcher.js +150 -0
- package/src/protocol.js +413 -0
- package/tests/comparison/run_comparison.js +279 -0
- package/tests/comparison/test_js4j.js +187 -0
- package/tests/comparison/test_py4j.py +171 -0
- package/tests/integration/gateway.integration.test.js +384 -0
- package/tests/unit/collections.test.js +313 -0
- package/tests/unit/exceptions.test.js +104 -0
- package/tests/unit/gateway.test.js +178 -0
- package/tests/unit/jvmView.test.js +126 -0
- package/tests/unit/launcher.test.js +27 -0
- package/tests/unit/protocol.test.js +280 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Comparison test runner.
|
|
6
|
+
*
|
|
7
|
+
* 1. Starts the Java TestEntryPoint gateway server
|
|
8
|
+
* 2. Runs test_py4j.py (captures py4j results)
|
|
9
|
+
* 3. Runs test_js4j.js (captures js4j results)
|
|
10
|
+
* 4. Compares the two result sets and reports differences
|
|
11
|
+
*
|
|
12
|
+
* Prerequisites:
|
|
13
|
+
* - Java installed (java on PATH)
|
|
14
|
+
* - py4j Java JAR on CLASSPATH (or provide PY4J_JAR env var)
|
|
15
|
+
* - Python 3 with py4j installed: pip install py4j
|
|
16
|
+
* - TestEntryPoint.java compiled: javac -cp <py4j.jar> java/TestEntryPoint.java -d java/build
|
|
17
|
+
*
|
|
18
|
+
* Usage:
|
|
19
|
+
* node tests/comparison/run_comparison.js
|
|
20
|
+
* PY4J_JAR=/path/to/py4j.jar node tests/comparison/run_comparison.js
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const { spawn, execSync } = require('child_process');
|
|
24
|
+
const fs = require('fs');
|
|
25
|
+
const path = require('path');
|
|
26
|
+
const net = require('net');
|
|
27
|
+
|
|
28
|
+
const ROOT = path.resolve(__dirname, '../..');
|
|
29
|
+
const JAVA_DIR = path.join(ROOT, 'java');
|
|
30
|
+
const BUILD_DIR = path.join(JAVA_DIR, 'build');
|
|
31
|
+
const PY4J_JAR = process.env.PY4J_JAR || findPy4jJar();
|
|
32
|
+
const GATEWAY_PORT = parseInt(process.env.GATEWAY_PORT || '25333', 10);
|
|
33
|
+
const PY_RESULTS = path.join(__dirname, 'comparison_results_py4j.json');
|
|
34
|
+
const JS_RESULTS = path.join(__dirname, 'comparison_results_js4j.json');
|
|
35
|
+
|
|
36
|
+
// Numeric comparison tolerance
|
|
37
|
+
const FLOAT_TOLERANCE = 1e-9;
|
|
38
|
+
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Helpers
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
function findPy4jJar() {
|
|
44
|
+
// Try common locations
|
|
45
|
+
const candidates = [
|
|
46
|
+
'/usr/share/py4j/py4j.jar',
|
|
47
|
+
'/usr/local/share/py4j/py4j.jar',
|
|
48
|
+
// pip install py4j puts the jar here
|
|
49
|
+
...(() => {
|
|
50
|
+
try {
|
|
51
|
+
const site = execSync('python3 -c "import py4j; import os; print(os.path.dirname(py4j.__file__))"', {
|
|
52
|
+
encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe']
|
|
53
|
+
}).trim();
|
|
54
|
+
return [
|
|
55
|
+
path.join(site, 'java', 'lib', 'py4j0.10.9.9.jar'),
|
|
56
|
+
path.join(site, 'java', 'lib', 'py4j0.10.9.8.jar'),
|
|
57
|
+
path.join(site, 'java', 'lib', 'py4j0.10.9.7.jar'),
|
|
58
|
+
...fs.readdirSync(path.join(site, 'java', 'lib')).map(f => path.join(site, 'java', 'lib', f)),
|
|
59
|
+
].filter(f => f.endsWith('.jar'));
|
|
60
|
+
} catch (_) {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
})(),
|
|
64
|
+
];
|
|
65
|
+
for (const c of candidates) {
|
|
66
|
+
try {
|
|
67
|
+
if (fs.existsSync(c)) return c;
|
|
68
|
+
} catch (_) {}
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function waitForPort(port, host, timeout = 15000) {
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
const start = Date.now();
|
|
76
|
+
function attempt() {
|
|
77
|
+
const sock = new net.Socket();
|
|
78
|
+
sock.setTimeout(500);
|
|
79
|
+
sock.on('connect', () => { sock.destroy(); resolve(); });
|
|
80
|
+
sock.on('error', () => {
|
|
81
|
+
sock.destroy();
|
|
82
|
+
if (Date.now() - start > timeout) {
|
|
83
|
+
reject(new Error(`Timeout waiting for port ${port}`));
|
|
84
|
+
} else {
|
|
85
|
+
setTimeout(attempt, 300);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
sock.on('timeout', () => {
|
|
89
|
+
sock.destroy();
|
|
90
|
+
setTimeout(attempt, 300);
|
|
91
|
+
});
|
|
92
|
+
sock.connect(port, host);
|
|
93
|
+
}
|
|
94
|
+
attempt();
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function run(cmd, args, opts = {}) {
|
|
99
|
+
return new Promise((resolve, reject) => {
|
|
100
|
+
const child = spawn(cmd, args, { stdio: 'inherit', ...opts });
|
|
101
|
+
child.on('close', (code) => {
|
|
102
|
+
if (code !== 0 && !opts.allowFailure) {
|
|
103
|
+
reject(new Error(`${cmd} exited with code ${code}`));
|
|
104
|
+
} else {
|
|
105
|
+
resolve(code);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
child.on('error', reject);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// Build Java
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
async function buildJava() {
|
|
117
|
+
if (!PY4J_JAR) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
'py4j JAR not found. Set PY4J_JAR=/path/to/py4j.jar or install py4j: pip install py4j'
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
console.log(`Using py4j JAR: ${PY4J_JAR}`);
|
|
123
|
+
|
|
124
|
+
fs.mkdirSync(BUILD_DIR, { recursive: true });
|
|
125
|
+
|
|
126
|
+
console.log('Compiling TestEntryPoint.java...');
|
|
127
|
+
await run('javac', [
|
|
128
|
+
'-cp', PY4J_JAR,
|
|
129
|
+
'-d', BUILD_DIR,
|
|
130
|
+
path.join(JAVA_DIR, 'TestEntryPoint.java'),
|
|
131
|
+
]);
|
|
132
|
+
console.log('Compiled successfully.\n');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
// Start gateway server
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
async function startGateway() {
|
|
140
|
+
console.log(`Starting Java GatewayServer on port ${GATEWAY_PORT}...`);
|
|
141
|
+
const child = spawn('java', [
|
|
142
|
+
'-cp', `${BUILD_DIR}:${PY4J_JAR}`,
|
|
143
|
+
'TestEntryPoint',
|
|
144
|
+
String(GATEWAY_PORT),
|
|
145
|
+
], { stdio: ['pipe', 'pipe', 'inherit'] });
|
|
146
|
+
|
|
147
|
+
// Wait for "GATEWAY_STARTED:" line
|
|
148
|
+
await new Promise((resolve, reject) => {
|
|
149
|
+
let buf = '';
|
|
150
|
+
child.stdout.setEncoding('utf8');
|
|
151
|
+
child.stdout.on('data', (chunk) => {
|
|
152
|
+
buf += chunk;
|
|
153
|
+
process.stdout.write(chunk);
|
|
154
|
+
if (buf.includes('GATEWAY_STARTED:')) resolve();
|
|
155
|
+
});
|
|
156
|
+
child.on('error', reject);
|
|
157
|
+
child.on('close', (code) => {
|
|
158
|
+
if (code !== 0) reject(new Error(`Gateway exited with code ${code}`));
|
|
159
|
+
});
|
|
160
|
+
setTimeout(() => reject(new Error('Timeout waiting for gateway')), 15000);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
await waitForPort(GATEWAY_PORT, '127.0.0.1');
|
|
164
|
+
console.log('Gateway is ready.\n');
|
|
165
|
+
return child;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
// Compare results
|
|
170
|
+
// ---------------------------------------------------------------------------
|
|
171
|
+
|
|
172
|
+
function floatEq(a, b) {
|
|
173
|
+
if (typeof a === 'number' && typeof b === 'number') {
|
|
174
|
+
return Math.abs(a - b) <= FLOAT_TOLERANCE * Math.max(1, Math.abs(a), Math.abs(b));
|
|
175
|
+
}
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function valuesMatch(py, js) {
|
|
180
|
+
if (py === null && js === null) return true;
|
|
181
|
+
if (py === null || js === null) return false;
|
|
182
|
+
if (typeof py === 'boolean' && typeof js === 'boolean') return py === js;
|
|
183
|
+
if (typeof py === 'number' && typeof js === 'number') {
|
|
184
|
+
return py === js || floatEq(py, js);
|
|
185
|
+
}
|
|
186
|
+
if (typeof py === 'string' && typeof js === 'string') return py === js;
|
|
187
|
+
return String(py) === String(js);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function compareResults(pyResults, jsResults) {
|
|
191
|
+
const allKeys = new Set([...Object.keys(pyResults), ...Object.keys(jsResults)]);
|
|
192
|
+
let passed = 0, failed = 0, skipped = 0;
|
|
193
|
+
|
|
194
|
+
console.log('\n' + '='.repeat(70));
|
|
195
|
+
console.log('COMPARISON RESULTS');
|
|
196
|
+
console.log('='.repeat(70));
|
|
197
|
+
|
|
198
|
+
for (const key of [...allKeys].sort()) {
|
|
199
|
+
const py = pyResults[key];
|
|
200
|
+
const js = jsResults[key];
|
|
201
|
+
|
|
202
|
+
if (!py) { console.log(` SKIP ${key} (missing from py4j results)`); skipped++; continue; }
|
|
203
|
+
if (!js) { console.log(` SKIP ${key} (missing from js4j results)`); skipped++; continue; }
|
|
204
|
+
|
|
205
|
+
const statusMatch = py.status === js.status;
|
|
206
|
+
const valueMatch = statusMatch && valuesMatch(py.value, js.value);
|
|
207
|
+
|
|
208
|
+
if (statusMatch && valueMatch) {
|
|
209
|
+
console.log(` PASS ${key} = ${JSON.stringify(py.value)}`);
|
|
210
|
+
passed++;
|
|
211
|
+
} else if (statusMatch && py.status !== 'ok') {
|
|
212
|
+
// Both threw an error (type matches) — consider it a pass for exception tests
|
|
213
|
+
console.log(` PASS ${key} [both raised ${py.status}]`);
|
|
214
|
+
passed++;
|
|
215
|
+
} else {
|
|
216
|
+
console.log(` FAIL ${key}`);
|
|
217
|
+
console.log(` py4j: status=${py.status} value=${JSON.stringify(py.value)}`);
|
|
218
|
+
console.log(` js4j: status=${js.status} value=${JSON.stringify(js.value)}`);
|
|
219
|
+
failed++;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
console.log('\n' + '='.repeat(70));
|
|
224
|
+
console.log(`Passed: ${passed} Failed: ${failed} Skipped: ${skipped} Total: ${allKeys.size}`);
|
|
225
|
+
console.log('='.repeat(70) + '\n');
|
|
226
|
+
|
|
227
|
+
return failed === 0;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ---------------------------------------------------------------------------
|
|
231
|
+
// Main
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
|
|
234
|
+
async function main() {
|
|
235
|
+
let gateway = null;
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
await buildJava();
|
|
239
|
+
gateway = await startGateway();
|
|
240
|
+
|
|
241
|
+
// Run py4j tests
|
|
242
|
+
console.log('Running py4j (Python) tests...\n');
|
|
243
|
+
await run('python3', [
|
|
244
|
+
path.join(__dirname, 'test_py4j.py'),
|
|
245
|
+
'--gateway-port', String(GATEWAY_PORT),
|
|
246
|
+
'--output', PY_RESULTS,
|
|
247
|
+
], { allowFailure: true });
|
|
248
|
+
|
|
249
|
+
// Run js4j tests
|
|
250
|
+
console.log('\nRunning js4j (Node.js) tests...\n');
|
|
251
|
+
await run('node', [
|
|
252
|
+
path.join(__dirname, 'test_js4j.js'),
|
|
253
|
+
'--gateway-port', String(GATEWAY_PORT),
|
|
254
|
+
'--output', JS_RESULTS,
|
|
255
|
+
], { allowFailure: true });
|
|
256
|
+
|
|
257
|
+
// Compare
|
|
258
|
+
if (!fs.existsSync(PY_RESULTS)) throw new Error(`py4j results file not found: ${PY_RESULTS}`);
|
|
259
|
+
if (!fs.existsSync(JS_RESULTS)) throw new Error(`js4j results file not found: ${JS_RESULTS}`);
|
|
260
|
+
|
|
261
|
+
const pyResults = JSON.parse(fs.readFileSync(PY_RESULTS, 'utf8'));
|
|
262
|
+
const jsResults = JSON.parse(fs.readFileSync(JS_RESULTS, 'utf8'));
|
|
263
|
+
|
|
264
|
+
const allPassed = compareResults(pyResults, jsResults);
|
|
265
|
+
process.exitCode = allPassed ? 0 : 1;
|
|
266
|
+
|
|
267
|
+
} catch (err) {
|
|
268
|
+
console.error('\nComparison runner error:', err.message);
|
|
269
|
+
process.exitCode = 1;
|
|
270
|
+
} finally {
|
|
271
|
+
if (gateway) {
|
|
272
|
+
console.log('Shutting down gateway...');
|
|
273
|
+
gateway.stdin.end();
|
|
274
|
+
gateway.kill('SIGTERM');
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
main();
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Comparison tests using js4j (Node.js implementation).
|
|
5
|
+
*
|
|
6
|
+
* Runs the exact same operations as test_py4j.py and writes results to
|
|
7
|
+
* comparison_results_js4j.json for comparison.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node test_js4j.js [--gateway-port 25333] [--output results_js4j.json]
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
const { JavaGateway, GatewayParameters, Js4JJavaError } = require('../../index');
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Argument parsing
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
const argv = process.argv.slice(2);
|
|
22
|
+
let gatewayPort = 25333;
|
|
23
|
+
let outputFile = 'comparison_results_js4j.json';
|
|
24
|
+
|
|
25
|
+
for (let i = 0; i < argv.length; i++) {
|
|
26
|
+
if (argv[i] === '--gateway-port' && argv[i + 1]) {
|
|
27
|
+
gatewayPort = parseInt(argv[++i], 10);
|
|
28
|
+
} else if (argv[i] === '--output' && argv[i + 1]) {
|
|
29
|
+
outputFile = argv[++i];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Test runner
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
const results = {};
|
|
37
|
+
|
|
38
|
+
async function runTest(name, fn) {
|
|
39
|
+
try {
|
|
40
|
+
const value = await fn();
|
|
41
|
+
results[name] = { status: 'ok', value: serialise(value) };
|
|
42
|
+
console.log(` PASS ${name} => ${JSON.stringify(results[name].value)}`);
|
|
43
|
+
} catch (err) {
|
|
44
|
+
if (err && (err.name === 'Js4JJavaError' || err.constructor.name === 'Js4JJavaError')) {
|
|
45
|
+
results[name] = { status: 'java_error', value: err.javaExceptionMessage || err.message };
|
|
46
|
+
console.log(` JAVA_ERR ${name}: ${err.message}`);
|
|
47
|
+
} else {
|
|
48
|
+
results[name] = { status: 'error', value: err.message || String(err) };
|
|
49
|
+
console.log(` ERROR ${name}: ${err.message}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function serialise(v) {
|
|
55
|
+
if (v === null || v === undefined) return null;
|
|
56
|
+
if (typeof v === 'boolean') return v;
|
|
57
|
+
if (typeof v === 'bigint') return Number(v);
|
|
58
|
+
if (typeof v === 'number') return v;
|
|
59
|
+
if (typeof v === 'string') return v;
|
|
60
|
+
// Java objects — return their string representation
|
|
61
|
+
return String(v);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// Main
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
async function main() {
|
|
68
|
+
const gateway = new JavaGateway(new GatewayParameters({ port: gatewayPort }));
|
|
69
|
+
await gateway.connect();
|
|
70
|
+
const ep = gateway.entry_point;
|
|
71
|
+
const jvm = gateway.jvm;
|
|
72
|
+
|
|
73
|
+
console.log('\n--- Arithmetic ---');
|
|
74
|
+
await runTest('add_int', () => ep.add(3, 4));
|
|
75
|
+
await runTest('add_negative', () => ep.add(-10, 5));
|
|
76
|
+
await runTest('add_doubles', () => ep.addDoubles(1.5, 2.5));
|
|
77
|
+
await runTest('multiply', () => ep.multiply(6, 7));
|
|
78
|
+
await runTest('divide', () => ep.divide(10.0, 4.0));
|
|
79
|
+
|
|
80
|
+
console.log('\n--- Strings ---');
|
|
81
|
+
await runTest('greet', () => ep.greet('World'));
|
|
82
|
+
await runTest('concatenate', () => ep.concatenate('foo', 'bar'));
|
|
83
|
+
await runTest('string_length', () => ep.stringLength('hello'));
|
|
84
|
+
await runTest('to_upper_case', () => ep.toUpperCase('hello'));
|
|
85
|
+
await runTest('contains_true', () => ep.containsSubstring('foobar', 'oba'));
|
|
86
|
+
await runTest('contains_false', () => ep.containsSubstring('foobar', 'xyz'));
|
|
87
|
+
await runTest('repeat_string', () => ep.repeatString('ab', 3));
|
|
88
|
+
|
|
89
|
+
console.log('\n--- Booleans ---');
|
|
90
|
+
await runTest('and_true', () => ep.andBool(true, true));
|
|
91
|
+
await runTest('and_false', () => ep.andBool(true, false));
|
|
92
|
+
await runTest('or_true', () => ep.orBool(false, true));
|
|
93
|
+
await runTest('or_false', () => ep.orBool(false, false));
|
|
94
|
+
await runTest('not_true', () => ep.notBool(true));
|
|
95
|
+
await runTest('not_false', () => ep.notBool(false));
|
|
96
|
+
|
|
97
|
+
console.log('\n--- Null handling ---');
|
|
98
|
+
await runTest('maybe_null_returns_null', () => ep.maybeNull(true));
|
|
99
|
+
await runTest('maybe_null_returns_str', () => ep.maybeNull(false));
|
|
100
|
+
|
|
101
|
+
console.log('\n--- Collections ---');
|
|
102
|
+
await runTest('list_size', async () => { const l = await ep.getStringList(); return l.size(); });
|
|
103
|
+
await runTest('list_get_0', async () => { const l = await ep.getStringList(); return l.get(0); });
|
|
104
|
+
await runTest('list_get_2', async () => { const l = await ep.getStringList(); return l.get(2); });
|
|
105
|
+
|
|
106
|
+
await runTest('int_list_get_0', async () => { const l = await ep.getIntList(); return l.get(0); });
|
|
107
|
+
await runTest('int_list_get_4', async () => { const l = await ep.getIntList(); return l.get(4); });
|
|
108
|
+
await runTest('int_list_size', async () => { const l = await ep.getIntList(); return l.size(); });
|
|
109
|
+
|
|
110
|
+
await runTest('set_size', async () => { const s = await ep.getStringSet(); return s.size(); });
|
|
111
|
+
await runTest('set_contains_one', async () => { const s = await ep.getStringSet(); return s.contains('one'); });
|
|
112
|
+
await runTest('set_contains_xxx', async () => { const s = await ep.getStringSet(); return s.contains('xxx'); });
|
|
113
|
+
|
|
114
|
+
await runTest('map_size', async () => { const m = await ep.getStringIntMap(); return m.size(); });
|
|
115
|
+
await runTest('map_get_a', async () => { const m = await ep.getStringIntMap(); return m.get('a'); });
|
|
116
|
+
await runTest('map_get_c', async () => { const m = await ep.getStringIntMap(); return m.get('c'); });
|
|
117
|
+
await runTest('map_contains_key_a', async () => { const m = await ep.getStringIntMap(); return m.containsKey('a'); });
|
|
118
|
+
await runTest('map_contains_key_z', async () => { const m = await ep.getStringIntMap(); return m.containsKey('z'); });
|
|
119
|
+
|
|
120
|
+
console.log('\n--- Type round-trips ---');
|
|
121
|
+
await runTest('echo_int_pos', () => ep.echoInt(42));
|
|
122
|
+
await runTest('echo_int_neg', () => ep.echoInt(-99));
|
|
123
|
+
await runTest('echo_long', () => ep.echoLong(1000000000000));
|
|
124
|
+
await runTest('echo_double', () => ep.echoDouble(3.14));
|
|
125
|
+
await runTest('echo_bool_true', () => ep.echoBool(true));
|
|
126
|
+
await runTest('echo_bool_false', () => ep.echoBool(false));
|
|
127
|
+
await runTest('echo_string', () => ep.echoString('js4j'));
|
|
128
|
+
|
|
129
|
+
console.log('\n--- Counter object ---');
|
|
130
|
+
await runTest('counter_initial', async () => {
|
|
131
|
+
const c = await ep.createCounter(10);
|
|
132
|
+
return c.getValue();
|
|
133
|
+
});
|
|
134
|
+
await runTest('counter_increment', async () => {
|
|
135
|
+
const c = await ep.createCounter(5);
|
|
136
|
+
await c.increment();
|
|
137
|
+
return c.getValue();
|
|
138
|
+
});
|
|
139
|
+
await runTest('counter_add', async () => {
|
|
140
|
+
const c = await ep.createCounter(3);
|
|
141
|
+
await c.add(7);
|
|
142
|
+
return c.getValue();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
console.log('\n--- Exceptions ---');
|
|
146
|
+
await runTest('throw_exception', () => ep.throwException('boom'));
|
|
147
|
+
await runTest('divide_by_zero', () => ep.divideInts(10, 0));
|
|
148
|
+
|
|
149
|
+
console.log('\n--- JVM namespace ---');
|
|
150
|
+
await runTest('Math_abs', () => jvm.java.lang.Math.abs(-42));
|
|
151
|
+
await runTest('Math_max', () => jvm.java.lang.Math.max(3, 7));
|
|
152
|
+
await runTest('Math_min', () => jvm.java.lang.Math.min(3, 7));
|
|
153
|
+
await runTest('Math_PI', () => gateway.getField(jvm.java.lang.Math, 'PI'));
|
|
154
|
+
await runTest('Integer_MAX', () => gateway.getField(jvm.java.lang.Integer, 'MAX_VALUE'));
|
|
155
|
+
await runTest('String_valueOf_int', () => jvm.java.lang.String.valueOf(123));
|
|
156
|
+
|
|
157
|
+
console.log('\n--- StringBuilder (constructor via JVM) ---');
|
|
158
|
+
await runTest('stringbuilder_basic', async () => {
|
|
159
|
+
const sb = await jvm.java.lang.StringBuilder('Hello');
|
|
160
|
+
await sb.append(' World');
|
|
161
|
+
return sb.toString();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
console.log('\n--- ArrayList (constructor via JVM) ---');
|
|
165
|
+
await runTest('arraylist_add_size', async () => {
|
|
166
|
+
const lst = await jvm.java.util.ArrayList();
|
|
167
|
+
await lst.add('x');
|
|
168
|
+
await lst.add('y');
|
|
169
|
+
return lst.size();
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
await gateway.close();
|
|
173
|
+
|
|
174
|
+
// Write results
|
|
175
|
+
const out = JSON.stringify(results, null, 2);
|
|
176
|
+
fs.writeFileSync(outputFile, out);
|
|
177
|
+
console.log(`\nResults written to ${outputFile}`);
|
|
178
|
+
|
|
179
|
+
const passed = Object.values(results).filter(r => r.status === 'ok').length;
|
|
180
|
+
const total = Object.keys(results).length;
|
|
181
|
+
console.log(`${passed}/${total} tests produced a result`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
main().catch((err) => {
|
|
185
|
+
console.error('Fatal error:', err);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
});
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Comparison tests using py4j (Python implementation).
|
|
4
|
+
|
|
5
|
+
Runs the same operations as test_js4j.js and writes results to
|
|
6
|
+
comparison_results_py4j.json so they can be compared with js4j output.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python test_py4j.py [--gateway-port 25333] [--output results_py4j.json]
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import json
|
|
14
|
+
import sys
|
|
15
|
+
import traceback
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
from py4j.java_gateway import JavaGateway, GatewayParameters
|
|
19
|
+
from py4j.protocol import Py4JJavaError
|
|
20
|
+
except ImportError:
|
|
21
|
+
print("ERROR: py4j is not installed. Run: pip install py4j", file=sys.stderr)
|
|
22
|
+
sys.exit(1)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def run_test(name, fn, results):
|
|
26
|
+
"""Run a single test and record the result."""
|
|
27
|
+
try:
|
|
28
|
+
value = fn()
|
|
29
|
+
results[name] = {"status": "ok", "value": value}
|
|
30
|
+
print(f" PASS {name} => {value!r}")
|
|
31
|
+
except Py4JJavaError as e:
|
|
32
|
+
results[name] = {"status": "java_error", "value": str(e.java_exception)}
|
|
33
|
+
print(f" JAVA_ERR {name}: {e.java_exception}")
|
|
34
|
+
except Exception as e:
|
|
35
|
+
results[name] = {"status": "error", "value": str(e)}
|
|
36
|
+
print(f" ERROR {name}: {e}")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def main():
|
|
40
|
+
parser = argparse.ArgumentParser()
|
|
41
|
+
parser.add_argument("--gateway-port", type=int, default=25333)
|
|
42
|
+
parser.add_argument("--output", default="comparison_results_py4j.json")
|
|
43
|
+
args = parser.parse_args()
|
|
44
|
+
|
|
45
|
+
gateway = JavaGateway(
|
|
46
|
+
gateway_parameters=GatewayParameters(port=args.gateway_port)
|
|
47
|
+
)
|
|
48
|
+
ep = gateway.entry_point
|
|
49
|
+
|
|
50
|
+
results = {}
|
|
51
|
+
|
|
52
|
+
print("\n--- Arithmetic ---")
|
|
53
|
+
run_test("add_int", lambda: ep.add(3, 4), results)
|
|
54
|
+
run_test("add_negative", lambda: ep.add(-10, 5), results)
|
|
55
|
+
run_test("add_doubles", lambda: ep.addDoubles(1.5, 2.5), results)
|
|
56
|
+
run_test("multiply", lambda: ep.multiply(6, 7), results)
|
|
57
|
+
run_test("divide", lambda: ep.divide(10.0, 4.0), results)
|
|
58
|
+
|
|
59
|
+
print("\n--- Strings ---")
|
|
60
|
+
run_test("greet", lambda: ep.greet("World"), results)
|
|
61
|
+
run_test("concatenate", lambda: ep.concatenate("foo", "bar"), results)
|
|
62
|
+
run_test("string_length", lambda: ep.stringLength("hello"), results)
|
|
63
|
+
run_test("to_upper_case", lambda: ep.toUpperCase("hello"), results)
|
|
64
|
+
run_test("contains_true", lambda: ep.containsSubstring("foobar", "oba"), results)
|
|
65
|
+
run_test("contains_false", lambda: ep.containsSubstring("foobar", "xyz"), results)
|
|
66
|
+
run_test("repeat_string", lambda: ep.repeatString("ab", 3), results)
|
|
67
|
+
|
|
68
|
+
print("\n--- Booleans ---")
|
|
69
|
+
run_test("and_true", lambda: ep.andBool(True, True), results)
|
|
70
|
+
run_test("and_false", lambda: ep.andBool(True, False), results)
|
|
71
|
+
run_test("or_true", lambda: ep.orBool(False, True), results)
|
|
72
|
+
run_test("or_false", lambda: ep.orBool(False, False), results)
|
|
73
|
+
run_test("not_true", lambda: ep.notBool(True), results)
|
|
74
|
+
run_test("not_false", lambda: ep.notBool(False), results)
|
|
75
|
+
|
|
76
|
+
print("\n--- Null handling ---")
|
|
77
|
+
run_test("maybe_null_returns_null", lambda: ep.maybeNull(True), results)
|
|
78
|
+
run_test("maybe_null_returns_str", lambda: ep.maybeNull(False), results)
|
|
79
|
+
|
|
80
|
+
print("\n--- Collections ---")
|
|
81
|
+
run_test("list_size", lambda: ep.getStringList().size(), results)
|
|
82
|
+
run_test("list_get_0", lambda: ep.getStringList().get(0), results)
|
|
83
|
+
run_test("list_get_2", lambda: ep.getStringList().get(2), results)
|
|
84
|
+
|
|
85
|
+
run_test("int_list_get_0", lambda: ep.getIntList().get(0), results)
|
|
86
|
+
run_test("int_list_get_4", lambda: ep.getIntList().get(4), results)
|
|
87
|
+
run_test("int_list_size", lambda: ep.getIntList().size(), results)
|
|
88
|
+
|
|
89
|
+
run_test("set_size", lambda: ep.getStringSet().size(), results)
|
|
90
|
+
run_test("set_contains_one", lambda: ep.getStringSet().contains("one"), results)
|
|
91
|
+
run_test("set_contains_xxx", lambda: ep.getStringSet().contains("xxx"), results)
|
|
92
|
+
|
|
93
|
+
run_test("map_size", lambda: ep.getStringIntMap().size(), results)
|
|
94
|
+
run_test("map_get_a", lambda: ep.getStringIntMap().get("a"), results)
|
|
95
|
+
run_test("map_get_c", lambda: ep.getStringIntMap().get("c"), results)
|
|
96
|
+
run_test("map_contains_key_a", lambda: ep.getStringIntMap().containsKey("a"), results)
|
|
97
|
+
run_test("map_contains_key_z", lambda: ep.getStringIntMap().containsKey("z"), results)
|
|
98
|
+
|
|
99
|
+
print("\n--- Type round-trips ---")
|
|
100
|
+
run_test("echo_int_pos", lambda: ep.echoInt(42), results)
|
|
101
|
+
run_test("echo_int_neg", lambda: ep.echoInt(-99), results)
|
|
102
|
+
run_test("echo_long", lambda: ep.echoLong(10**12), results)
|
|
103
|
+
run_test("echo_double", lambda: ep.echoDouble(3.14), results)
|
|
104
|
+
run_test("echo_bool_true", lambda: ep.echoBool(True), results)
|
|
105
|
+
run_test("echo_bool_false",lambda: ep.echoBool(False), results)
|
|
106
|
+
run_test("echo_string", lambda: ep.echoString("js4j"), results)
|
|
107
|
+
|
|
108
|
+
print("\n--- Counter object ---")
|
|
109
|
+
run_test("counter_initial", lambda: ep.createCounter(10).getValue(), results)
|
|
110
|
+
run_test("counter_increment", lambda: (
|
|
111
|
+
lambda c: (c.increment(), c.getValue())[1]
|
|
112
|
+
)(ep.createCounter(5)), results)
|
|
113
|
+
run_test("counter_add", lambda: (
|
|
114
|
+
lambda c: (c.add(7), c.getValue())[1]
|
|
115
|
+
)(ep.createCounter(3)), results)
|
|
116
|
+
|
|
117
|
+
print("\n--- Exceptions ---")
|
|
118
|
+
run_test("throw_exception", lambda: ep.throwException("boom"), results)
|
|
119
|
+
run_test("divide_by_zero", lambda: ep.divideInts(10, 0), results)
|
|
120
|
+
|
|
121
|
+
print("\n--- JVM namespace ---")
|
|
122
|
+
jvm = gateway.jvm
|
|
123
|
+
run_test("Math_abs", lambda: jvm.java.lang.Math.abs(-42), results)
|
|
124
|
+
run_test("Math_max", lambda: jvm.java.lang.Math.max(3, 7), results)
|
|
125
|
+
run_test("Math_min", lambda: jvm.java.lang.Math.min(3, 7), results)
|
|
126
|
+
run_test("Math_PI", lambda: float(jvm.java.lang.Math.PI), results)
|
|
127
|
+
run_test("Integer_MAX", lambda: jvm.java.lang.Integer.MAX_VALUE, results)
|
|
128
|
+
run_test("String_valueOf_int", lambda: jvm.java.lang.String.valueOf(123), results)
|
|
129
|
+
|
|
130
|
+
print("\n--- StringBuilder (constructor via JVM) ---")
|
|
131
|
+
run_test("stringbuilder_basic", lambda: (
|
|
132
|
+
lambda sb: (sb.append(" World"), str(sb.toString()))[1]
|
|
133
|
+
)(jvm.java.lang.StringBuilder("Hello")), results)
|
|
134
|
+
|
|
135
|
+
print("\n--- ArrayList (constructor via JVM) ---")
|
|
136
|
+
run_test("arraylist_add_size", lambda: (
|
|
137
|
+
lambda lst: (lst.add("x"), lst.add("y"), lst.size())[2]
|
|
138
|
+
)(jvm.java.util.ArrayList()), results)
|
|
139
|
+
|
|
140
|
+
gateway.close()
|
|
141
|
+
|
|
142
|
+
# Serialise — convert Java/py4j types to plain Python types
|
|
143
|
+
def serialise(v):
|
|
144
|
+
if v is None:
|
|
145
|
+
return None
|
|
146
|
+
if isinstance(v, bool):
|
|
147
|
+
return bool(v)
|
|
148
|
+
if isinstance(v, int):
|
|
149
|
+
return int(v)
|
|
150
|
+
if isinstance(v, float):
|
|
151
|
+
return float(v)
|
|
152
|
+
return str(v)
|
|
153
|
+
|
|
154
|
+
serialisable = {}
|
|
155
|
+
for key, rec in results.items():
|
|
156
|
+
serialisable[key] = {
|
|
157
|
+
"status": rec["status"],
|
|
158
|
+
"value": serialise(rec["value"]),
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
with open(args.output, "w") as f:
|
|
162
|
+
json.dump(serialisable, f, indent=2)
|
|
163
|
+
|
|
164
|
+
print(f"\nResults written to {args.output}")
|
|
165
|
+
passed = sum(1 for r in results.values() if r["status"] == "ok")
|
|
166
|
+
total = len(results)
|
|
167
|
+
print(f"{passed}/{total} tests produced a result (errors are expected for exception tests)")
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
if __name__ == "__main__":
|
|
171
|
+
main()
|