From 5ad1330ad8c1128a724ca6f62d9d5410cbeb2593 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ha=C5=82as?= Date: Mon, 6 Jul 2026 22:23:28 +0200 Subject: [PATCH] test: add ZVecException isolation tests for error code strings, constructor, chaining, and error details (closes #99) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added: - 4 unit .phpt tests covering getErrorCodeString() (codes 0-10, 99, -1), constructor parameter combinations, exception chaining (3 levels, Error, RuntimeException), and error detail boundary values (0, PHP_INT_MAX, empty strings, unicode, instance independence) - 1 integration .phpt test with real FFI round-trip errors: INVALID_ARGUMENT from open(), ALREADY_EXISTS from duplicate PK, invalid filter syntax, exception chaining with error details propagation - examples/08_error_handling.php demonstrating all error handling patterns All tests verified with `php run-tests.php -n tests/` — no regressions --- CHANGELOG.md | 12 ++ examples/08_error_handling.php | 125 +++++++++++++++++ tests/test_exception_chaining.phpt | 114 ++++++++++++++++ tests/test_exception_constructor.phpt | 143 ++++++++++++++++++++ tests/test_exception_error_code_string.phpt | 71 ++++++++++ tests/test_exception_error_details.phpt | 125 +++++++++++++++++ tests/test_exception_integration.phpt | 105 ++++++++++++++ 7 files changed, 695 insertions(+) create mode 100644 examples/08_error_handling.php create mode 100644 tests/test_exception_chaining.phpt create mode 100644 tests/test_exception_constructor.phpt create mode 100644 tests/test_exception_error_code_string.phpt create mode 100644 tests/test_exception_error_details.phpt create mode 100644 tests/test_exception_integration.phpt diff --git a/CHANGELOG.md b/CHANGELOG.md index 53be310..de0b34c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- **TEST-001: ZVecException isolation tests for error code strings, constructor, chaining, and error details** (#99) + - Added 4 unit test files for `ZVecException` class: + - `test_exception_error_code_string.phpt` — getErrorCodeString() mapping for codes 0-10, 99, and -1 + - `test_exception_constructor.phpt` — parameter combinations (default, message, code, all params, partial details) + - `test_exception_chaining.phpt` — exception chaining with RuntimeException, ZVecException, custom Throwable, deep chaining + - `test_exception_error_details.phpt` — getErrorFile/Line/Function with boundary values, empty strings, unicode, chaining preservation + - Added 1 integration test: + - `test_exception_integration.phpt` — real FFI round-trip errors (INVALID_ARGUMENT, ALREADY_EXISTS, invalid filter, chaining) + - Added `examples/08_error_handling.php` — comprehensive error handling patterns demonstration + - Each test uses `try-finally` with `uniqid()` temp directory and cleanup + - Tests skip properly when native zvec extension is loaded (FFI-only methods) + - **DOC-001: Added class-level PHPDoc to all major classes for IDE tooling** (#60) - Added class-level PHPDoc blocks to all 15 source files: ZVec, ZVecException, ZVecCollectionOptions, ZVecCollectionStats, ZVecFieldSchema, ZVecIndexParams, ZVecQueryInterface, ZVecVectorQuery, ZVecGroupByVectorQuery, ZVecSchema, ZVecDoc, ZVecRerankedDoc, ZVecRrfReRanker, ZVecWeightedReRanker, ZVecReRanker - Each block follows a consistent format: one-line purpose, usage paragraph, and `@see` cross-references diff --git a/examples/08_error_handling.php b/examples/08_error_handling.php new file mode 100644 index 0000000..1805662 --- /dev/null +++ b/examples/08_error_handling.php @@ -0,0 +1,125 @@ +getCode(), + $e->getErrorCodeString(), + $e->getMessage(), + ); +} + +echo "\n2. Error details (file, line, function):\n"; +try { + $c = ZVec::open('/tmp/nonexistent_' . uniqid()); +} catch (ZVecException $e) { + printf( + " File: %s\n Line: %d\n Function: %s\n", + $e->getErrorFile() ?? 'N/A', + $e->getErrorLine() ?? 0, + $e->getErrorFunction() ?? 'N/A', + ); +} + +echo "\n3. All error code strings:\n"; +foreach ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 99] as $code) { + $e = new ZVecException('test', $code); + printf(" Code %-2d → %s\n", $code, $e->getErrorCodeString()); +} + +echo "\n4. Exception chaining:\n"; +try { + try { + // Inner error + throw new ZVecException('FFI call failed: zvec_collection_open', 3); + } catch (ZVecException $inner) { + // Wrap with more context + throw new ZVecException( + 'Failed to open collection', + 8, + previous: $inner, + errorFile: $inner->getErrorFile(), + errorLine: $inner->getErrorLine(), + errorFunction: $inner->getErrorFunction(), + ); + } +} catch (ZVecException $outer) { + printf( + " Outer: code=%d (%s), message=%s\n", + $outer->getCode(), + $outer->getErrorCodeString(), + $outer->getMessage(), + ); + if ($outer->getPrevious() !== null) { + /** @var ZVecException $prev */ + $prev = $outer->getPrevious(); + printf( + " Inner: code=%d (%s), message=%s\n", + $prev->getCode(), + $prev->getErrorCodeString(), + $prev->getMessage(), + ); + } +} + +echo "\n5. try-finally cleanup pattern with error tracking:\n"; +$path = __DIR__ . '/../test_dbs/example_08_' . uniqid(); +try { + $schema = new ZVecSchema('error_demo'); + $schema->setMaxDocCountPerSegment(1000) + ->addInt64('id', nullable: false) + ->addVectorFp32('v', dimension: 4); + + $c = ZVec::create($path, $schema); + echo " Collection created successfully\n"; + + $doc = new ZVecDoc('doc1'); + $doc->setInt64('id', 1); + $doc->setVectorFp32('v', [0.1, 0.2, 0.3, 0.4]); + $c->insert($doc); + echo " Document inserted\n"; + + $c->close(); + echo " Collection closed\n"; +} catch (ZVecException $e) { + printf( + " ERROR: code=%d (%s) — %s\n", + $e->getCode(), + $e->getErrorCodeString(), + $e->getMessage(), + ); +} finally { + exec("rm -rf " . escapeshellarg($path)); + echo " Cleanup completed\n"; +} + +echo "\nAll examples completed.\n"; diff --git a/tests/test_exception_chaining.phpt b/tests/test_exception_chaining.phpt new file mode 100644 index 0000000..5d441a2 --- /dev/null +++ b/tests/test_exception_chaining.phpt @@ -0,0 +1,114 @@ +--TEST-- +ZVecException: exception chaining with previous Throwable +--SKIPIF-- + + +--FILE-- +getPrevious() !== $prev) { + echo "FAIL: Previous exception not set correctly\n"; + exit(1); +} +if ($e->getPrevious()->getMessage() !== 'Database connection failed') { + echo "FAIL: Previous exception message mismatch\n"; + exit(1); +} +echo "RuntimeException chaining OK\n"; + +// Test 2: Chain another ZVecException +$inner = new ZVecException('Inner FFI error', 3); +$outer = new ZVecException('Operation failed', 8, $inner); + +if ($outer->getPrevious() !== $inner) { + echo "FAIL: Inner exception not set correctly\n"; + exit(1); +} +if ($outer->getPrevious()->getCode() !== 3) { + echo "FAIL: Inner exception code mismatch\n"; + exit(1); +} +echo "ZVecException chaining OK\n"; + +// Test 3: Chain a custom Throwable +$custom = new \Error('Custom fatal error'); +$e = new ZVecException('Wrapper error', 5, $custom); + +if ($e->getPrevious() !== $custom) { + echo "FAIL: Custom Throwable not set correctly\n"; + exit(1); +} +if ($e->getPrevious()->getMessage() !== 'Custom fatal error') { + echo "FAIL: Custom Throwable message mismatch\n"; + exit(1); +} +echo "Custom Throwable chaining OK\n"; + +// Test 4: No previous exception (default null) +$e = new ZVecException('Simple error', 1); +if ($e->getPrevious() !== null) { + echo "FAIL: Previous should be null when not set\n"; + exit(1); +} +echo "No previous exception OK\n"; + +// Test 5: Deep chaining (3 levels) +$level3 = new ZVecException('Level 3: C-level error', 3); +$level2 = new ZVecException('Level 2: FFI wrapper error', 5, $level3); +$level1 = new ZVecException('Level 1: PHP operation failed', 8, $level2); + +if ($level1->getPrevious() !== $level2) { + echo "FAIL: Level 1→2 chain broken\n"; + exit(1); +} +if ($level1->getPrevious()->getPrevious() !== $level3) { + echo "FAIL: Level 2→3 chain broken\n"; + exit(1); +} +if ($level1->getPrevious()->getPrevious()->getPrevious() !== null) { + echo "FAIL: Level 3 should have no previous\n"; + exit(1); +} +echo "Deep chaining (3 levels) OK\n"; + +// Test 6: getCode() on chained exceptions +$inner = new ZVecException('Inner error', 3); +$outer = new ZVecException('Outer error', 8, $inner); + +if ($outer->getCode() !== 8) { + echo "FAIL: Outer code should be 8\n"; + exit(1); +} +if ($outer->getPrevious()->getCode() !== 3) { + echo "FAIL: Inner code should be 3\n"; + exit(1); +} +echo "Chained exception codes OK\n"; + +// Test 7: getMessage() on chained exceptions +if ($outer->getMessage() !== 'Outer error') { + echo "FAIL: Outer message mismatch\n"; + exit(1); +} +if ($outer->getPrevious()->getMessage() !== 'Inner error') { + echo "FAIL: Inner message mismatch\n"; + exit(1); +} +echo "Chained exception messages OK\n"; + +echo "All exception chaining tests passed\n"; +?> +--EXPECT-- +RuntimeException chaining OK +ZVecException chaining OK +Custom Throwable chaining OK +No previous exception OK +Deep chaining (3 levels) OK +Chained exception codes OK +Chained exception messages OK +All exception chaining tests passed diff --git a/tests/test_exception_constructor.phpt b/tests/test_exception_constructor.phpt new file mode 100644 index 0000000..75d0937 --- /dev/null +++ b/tests/test_exception_constructor.phpt @@ -0,0 +1,143 @@ +--TEST-- +ZVecException: constructor with various parameter combinations +--SKIPIF-- + + +--FILE-- +getMessage() !== '') { + echo "FAIL: Default message should be empty, got: " . $e->getMessage() . "\n"; + exit(1); +} +if ($e->getCode() !== 0) { + echo "FAIL: Default code should be 0, got: " . $e->getCode() . "\n"; + exit(1); +} +if ($e->getErrorFile() !== null) { + echo "FAIL: Default errorFile should be null\n"; + exit(1); +} +if ($e->getErrorLine() !== null) { + echo "FAIL: Default errorLine should be null\n"; + exit(1); +} +if ($e->getErrorFunction() !== null) { + echo "FAIL: Default errorFunction should be null\n"; + exit(1); +} +echo "Default constructor OK\n"; + +// Test 2: Constructor with message only +$e = new ZVecException('Something went wrong'); +if ($e->getMessage() !== 'Something went wrong') { + echo "FAIL: Message mismatch\n"; + exit(1); +} +if ($e->getCode() !== 0) { + echo "FAIL: Code should be 0 when not specified\n"; + exit(1); +} +echo "Message-only constructor OK\n"; + +// Test 3: Constructor with message and code +$e = new ZVecException('Not found', 1); +if ($e->getMessage() !== 'Not found') { + echo "FAIL: Message mismatch\n"; + exit(1); +} +if ($e->getCode() !== 1) { + echo "FAIL: Code should be 1\n"; + exit(1); +} +echo "Message+code constructor OK\n"; + +// Test 4: Constructor with all params (message, code, previous, errorFile, errorLine, errorFunction) +$prev = new RuntimeException('Previous error'); +$e = new ZVecException( + message: 'Detailed error', + code: 5, + previous: $prev, + errorFile: 'test.php', + errorLine: 42, + errorFunction: 'testFunction' +); +if ($e->getMessage() !== 'Detailed error') { + echo "FAIL: Message mismatch\n"; + exit(1); +} +if ($e->getCode() !== 5) { + echo "FAIL: Code mismatch\n"; + exit(1); +} +if ($e->getPrevious() !== $prev) { + echo "FAIL: Previous exception mismatch\n"; + exit(1); +} +if ($e->getErrorFile() !== 'test.php') { + echo "FAIL: errorFile mismatch: " . $e->getErrorFile() . "\n"; + exit(1); +} +if ($e->getErrorLine() !== 42) { + echo "FAIL: errorLine mismatch: " . $e->getErrorLine() . "\n"; + exit(1); +} +if ($e->getErrorFunction() !== 'testFunction') { + echo "FAIL: errorFunction mismatch\n"; + exit(1); +} +echo "Full params constructor OK\n"; + +// Test 5: Constructor with partial error details +$e = new ZVecException('Error', 3, errorFile: 'script.php', errorLine: 10); +if ($e->getErrorFile() !== 'script.php') { + echo "FAIL: errorFile mismatch\n"; + exit(1); +} +if ($e->getErrorLine() !== 10) { + echo "FAIL: errorLine mismatch\n"; + exit(1); +} +if ($e->getErrorFunction() !== null) { + echo "FAIL: errorFunction should be null\n"; + exit(1); +} +echo "Partial error details OK\n"; + +// Test 6: Constructor with errorFunction only +$e = new ZVecException('Error', 7, errorFunction: 'someFunction'); +if ($e->getErrorFunction() !== 'someFunction') { + echo "FAIL: errorFunction mismatch\n"; + exit(1); +} +if ($e->getErrorFile() !== null) { + echo "FAIL: errorFile should be null\n"; + exit(1); +} +if ($e->getErrorLine() !== null) { + echo "FAIL: errorLine should be null\n"; + exit(1); +} +echo "Error function only OK\n"; + +// Test 7: Exception extends RuntimeException +if (!$e instanceof RuntimeException) { + echo "FAIL: ZVecException should extend RuntimeException\n"; + exit(1); +} +echo "Extends RuntimeException OK\n"; + +echo "All constructor tests passed\n"; +?> +--EXPECT-- +Default constructor OK +Message-only constructor OK +Message+code constructor OK +Full params constructor OK +Partial error details OK +Error function only OK +Extends RuntimeException OK +All constructor tests passed diff --git a/tests/test_exception_error_code_string.phpt b/tests/test_exception_error_code_string.phpt new file mode 100644 index 0000000..a6e9360 --- /dev/null +++ b/tests/test_exception_error_code_string.phpt @@ -0,0 +1,71 @@ +--TEST-- +ZVecException: getErrorCodeString() returns correct string for codes 0-10 and unrecognized +--SKIPIF-- + + +--FILE-- + 'OK', + 1 => 'NOT_FOUND', + 2 => 'ALREADY_EXISTS', + 3 => 'INVALID_ARGUMENT', + 4 => 'PERMISSION_DENIED', + 5 => 'FAILED_PRECONDITION', + 6 => 'RESOURCE_EXHAUSTED', + 7 => 'UNAVAILABLE', + 8 => 'INTERNAL_ERROR', + 9 => 'NOT_SUPPORTED', + 10 => 'UNKNOWN', +]; + +foreach ($tests as $code => $expected) { + $e = new ZVecException('test', $code); + $actual = $e->getErrorCodeString(); + if ($actual !== $expected) { + echo "FAIL: code=$code expected=$expected actual=$actual\n"; + exit(1); + } + echo "code=$code -> $actual OK\n"; +} + +// Test unrecognized code (default case) +$e = new ZVecException('test', 99); +$actual = $e->getErrorCodeString(); +if ($actual !== 'UNRECOGNIZED') { + echo "FAIL: code=99 expected=UNRECOGNIZED actual=$actual\n"; + exit(1); +} +echo "code=99 -> UNRECOGNIZED OK\n"; + +// Test negative code +$e = new ZVecException('test', -1); +$actual = $e->getErrorCodeString(); +if ($actual !== 'UNRECOGNIZED') { + echo "FAIL: code=-1 expected=UNRECOGNIZED actual=$actual\n"; + exit(1); +} +echo "code=-1 -> UNRECOGNIZED OK\n"; + +echo "All error code string tests passed\n"; +?> +--EXPECT-- +code=0 -> OK OK +code=1 -> NOT_FOUND OK +code=2 -> ALREADY_EXISTS OK +code=3 -> INVALID_ARGUMENT OK +code=4 -> PERMISSION_DENIED OK +code=5 -> FAILED_PRECONDITION OK +code=6 -> RESOURCE_EXHAUSTED OK +code=7 -> UNAVAILABLE OK +code=8 -> INTERNAL_ERROR OK +code=9 -> NOT_SUPPORTED OK +code=10 -> UNKNOWN OK +code=99 -> UNRECOGNIZED OK +code=-1 -> UNRECOGNIZED OK +All error code string tests passed diff --git a/tests/test_exception_error_details.phpt b/tests/test_exception_error_details.phpt new file mode 100644 index 0000000..77246d4 --- /dev/null +++ b/tests/test_exception_error_details.phpt @@ -0,0 +1,125 @@ +--TEST-- +ZVecException: error details (getErrorFile, getErrorLine, getErrorFunction) +--SKIPIF-- + + +--FILE-- +getErrorFile() !== 'zvec_ffi.cc') { + echo "FAIL: getErrorFile() mismatch: " . $e->getErrorFile() . "\n"; + exit(1); +} +if ($e->getErrorLine() !== 128) { + echo "FAIL: getErrorLine() mismatch: " . $e->getErrorLine() . "\n"; + exit(1); +} +if ($e->getErrorFunction() !== 'zvec_collection_query') { + echo "FAIL: getErrorFunction() mismatch: " . $e->getErrorFunction() . "\n"; + exit(1); +} +echo "All error details set and retrieved OK\n"; + +// Test 2: Error line boundary values +$e = new ZVecException('Error', 3, errorLine: 0); +if ($e->getErrorLine() !== 0) { + echo "FAIL: errorLine=0 should be accepted\n"; + exit(1); +} +echo "errorLine=0 OK\n"; + +$e = new ZVecException('Error', 3, errorLine: PHP_INT_MAX); +if ($e->getErrorLine() !== PHP_INT_MAX) { + echo "FAIL: errorLine=PHP_INT_MAX should be accepted\n"; + exit(1); +} +echo "errorLine=PHP_INT_MAX OK\n"; + +// Test 3: Empty strings for file and function +$e = new ZVecException('Error', 3, errorFile: '', errorFunction: ''); +if ($e->getErrorFile() !== '') { + echo "FAIL: Empty errorFile should be accepted\n"; + exit(1); +} +if ($e->getErrorFunction() !== '') { + echo "FAIL: Empty errorFunction should be accepted\n"; + exit(1); +} +echo "Empty strings for file/function OK\n"; + +// Test 4: Long file path +$longPath = str_repeat('a', 200) . '.cc'; +$e = new ZVecException('Error', 3, errorFile: $longPath); +if ($e->getErrorFile() !== $longPath) { + echo "FAIL: Long errorFile path mismatch\n"; + exit(1); +} +echo "Long file path OK\n"; + +// Test 5: Unicode in error details +$e = new ZVecException('Error', 5, errorFile: 'źródło.cc', errorFunction: 'funkcja_testowa'); +if ($e->getErrorFile() !== 'źródło.cc') { + echo "FAIL: Unicode errorFile mismatch\n"; + exit(1); +} +if ($e->getErrorFunction() !== 'funkcja_testowa') { + echo "FAIL: Unicode errorFunction mismatch\n"; + exit(1); +} +echo "Unicode error details OK\n"; + +// Test 6: Error details are independent on different instances +$e1 = new ZVecException('Error 1', 1, errorFile: 'file1.cc', errorLine: 10, errorFunction: 'func1'); +$e2 = new ZVecException('Error 2', 2, errorFile: 'file2.cc', errorLine: 20, errorFunction: 'func2'); + +if ($e1->getErrorFile() !== 'file1.cc' || $e2->getErrorFile() !== 'file2.cc') { + echo "FAIL: Error details should be independent per instance\n"; + exit(1); +} +if ($e1->getErrorLine() !== 10 || $e2->getErrorLine() !== 20) { + echo "FAIL: Error lines should be independent per instance\n"; + exit(1); +} +if ($e1->getErrorFunction() !== 'func1' || $e2->getErrorFunction() !== 'func2') { + echo "FAIL: Error functions should be independent per instance\n"; + exit(1); +} +echo "Independent error details on different instances OK\n"; + +// Test 7: Error details preserved through chaining +$inner = new ZVecException('Inner', 3, errorFile: 'inner.cc', errorLine: 5, errorFunction: 'inner_func'); +$outer = new ZVecException('Outer', 8, $inner, errorFile: 'outer.cc', errorLine: 100, errorFunction: 'outer_func'); + +if ($outer->getErrorFile() !== 'outer.cc') { + echo "FAIL: Outer errorFile should be 'outer.cc'\n"; + exit(1); +} +if ($outer->getPrevious()->getErrorFile() !== 'inner.cc') { + echo "FAIL: Inner errorFile should be 'inner.cc'\n"; + exit(1); +} +if ($outer->getPrevious()->getErrorLine() !== 5) { + echo "FAIL: Inner errorLine should be 5\n"; + exit(1); +} +if ($outer->getPrevious()->getErrorFunction() !== 'inner_func') { + echo "FAIL: Inner errorFunction should be 'inner_func'\n"; + exit(1); +} +echo "Error details preserved through chaining OK\n"; + +echo "All error details tests passed\n"; +?> +--EXPECT-- +All error details set and retrieved OK +errorLine=0 OK +errorLine=PHP_INT_MAX OK +Empty strings for file/function OK +Long file path OK +Unicode error details OK +Independent error details on different instances OK +Error details preserved through chaining OK +All error details tests passed diff --git a/tests/test_exception_integration.phpt b/tests/test_exception_integration.phpt new file mode 100644 index 0000000..163eac7 --- /dev/null +++ b/tests/test_exception_integration.phpt @@ -0,0 +1,105 @@ +--TEST-- +ZVecException: integration test with real FFI errors (round-trip through FFI) +--SKIPIF-- + + +--FILE-- +getCode(), $e->getErrorCodeString()); + $file = $e->getErrorFile(); + $line = $e->getErrorLine(); + $func = $e->getErrorFunction(); + if ($file !== null && $line !== null && $func !== null) { + printf("PASS: details present (file=%s, line=%d, func=%s)\n", $file, $line, $func); + } else { + echo "FAIL: error details should be present with verboseErrors=true\n"; + exit(1); + } + } + + // Test 2: Trigger ALREADY_EXISTS (code 2) by duplicate PK insert + $schema = new ZVecSchema('exception_test'); + $schema->setMaxDocCountPerSegment(1000) + ->addInt64('id', nullable: false, withInvertIndex: true) + ->addVectorFp32('v', dimension: 4, metricType: ZVecSchema::METRIC_IP); + + $c = ZVec::create($path, $schema); + $doc = new ZVecDoc('duplicate_test'); + $doc->setInt64('id', 1)->setVectorFp32('v', [0.1, 0.2, 0.3, 0.4]); + $c->insert($doc); + + try { + $c->insert($doc); // Same PK → ALREADY_EXISTS + echo "FAIL: Should have thrown exception on duplicate insert\n"; + exit(1); + } catch (ZVecException $e) { + printf("PASS: Duplicate PK code=%d (%s)\n", $e->getCode(), $e->getErrorCodeString()); + if ($e->getErrorFile() !== null) { + printf("PASS: error file: %s\n", $e->getErrorFile()); + } else { + echo "FAIL: error file should be present\n"; + exit(1); + } + } + + // Test 3: Trigger INVALID_ARGUMENT (code 3) with invalid filter + $c->optimize(); + try { + $c->query('v', [0.1, 0.2, 0.3, 0.4], filter: 'bad!!filter!!'); + echo "FAIL: Should have thrown exception on invalid filter\n"; + exit(1); + } catch (ZVecException $e) { + printf("PASS: Invalid filter code=%d (%s)\n", $e->getCode(), $e->getErrorCodeString()); + if ($e->getErrorFile() !== null) { + printf("PASS: error file: %s\n", $e->getErrorFile()); + } + } + $c->close(); + + // Test 4: Exception chaining with error details preserved + try { + $inner = new ZVecException('FFI call failed', 3, errorFile: 'zvec_ffi.cc', errorLine: 256, errorFunction: 'zvec_collection_open'); + throw new ZVecException( + 'Operation failed', + 8, + previous: $inner, + errorFile: $inner->getErrorFile(), + errorLine: $inner->getErrorLine(), + errorFunction: $inner->getErrorFunction(), + ); + } catch (ZVecException $outer) { + printf("PASS: Chained exception outer=%d inner=%d\n", $outer->getCode(), $outer->getPrevious()->getCode()); + if ($outer->getErrorFile() !== null) { + printf("PASS: Outer error file: %s\n", $outer->getErrorFile()); + } + } + + echo "All integration tests passed\n"; +} finally { + exec("rm -rf " . escapeshellarg($path) . " " . escapeshellarg($logDir)); +} +?> +--EXPECTF-- +PASS: code=%d (%s) +PASS: details present (file=%s, line=%d, func=%s) +PASS: Duplicate PK code=%d (%s) +PASS: error file: %s +PASS: Invalid filter code=%d (%s) +PASS: error file: %s +PASS: Chained exception outer=%d inner=%d +PASS: Outer error file: %s +All integration tests passed