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.
@@ -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()