SLiM protocol conformance audit
Scope and evidence
This audit compares the Perl service with the FitNesse SLiM protocol specification, the official exception-handling and data-type documentation, and the installed FitNesse v20251025 implementation. The installed /opt/fitnesse/fitnesse.jar declares Implementation-Version: 20251025 and has SHA-256 824596f90381a9637abd4d5b2aa0f4f03f2ef5e5716399c41b2ae8889d470d11; inspected classes include SlimVersion, SlimStreamReader, SlimSerializer, SlimDeserializer, SlimServer, ListExecutor, StatementExecutor, SlimExecutionContext, VariableStore, SlimSymbol, ConverterRegistry, VoidConverter, and the standard converters. Source locations are under fitnesse/slim and fitnesse/slim/protocol.
The test column below describes direct regression coverage in this repository, not just acceptance-page outcomes. The current acceptance inventory executes 50 upstream pages against the external Perl service; 45 conform, with five documented FitNesse harness/rendering/environment limitations. That acceptance count is useful integration evidence, but it does not establish untested protocol branches.
Conformance matrix
| Requirement | Authoritative source | Perl implementation | Existing / added tests | Verified behavior and gaps |
|---|---|---|---|---|
| Greeting and session lifecycle over TCP and pipes | Protocol guide, “Slim Server”; installed SlimServer, SlimPipeSocket, and SlimVersion | Server.pm, perl-slim | t/server.t, t/server-concurrency.t, t/pipe-transport.t, individual acceptance suites over both transports | TCP and port 1 both send Slim -- V0.5\n for the pinned FitNesse release; pipe mode sends it over stdin/stdout. Both modes share the same session and executor. Repeated frames, framed bye, and clean boundary EOF are covered. No request-side version negotiation is implemented. |
| Framing length counts transmitted bytes; handles partial I/O | Protocol guide, “Slim Server”; installed v20251025 SlimStreamReader and StreamReader | Server::_read_exact, Server::_write, Server::_read_message | t/server-frame.t, t/server-malformed-input.t, t/server-unicode-frame.t | Reads exactly the declared payload across short reads and EINTR; rejects nonnumeric, negative, missing-colon, truncated, over-limit, and excessively long prefixes before payload allocation. Six-or-more-digit and leading-zero expanded fields are accepted. Default frame cap is 67,108,864 bytes; TEST_SLIM_MAX_FRAME_BYTES may be set from 1 through 1,073,741,824. The cap is a Perl policy, not a FitNesse limit. Java v20251025 parses an int, has no configured frame cap, reports malformed prefixes as I/O failures, and closes only that connection. |
| UTF-8 transport vs UTF-16 list-string lengths | Protocol guide, “Strings and Lists” and framing notes; installed SlimSerializer / SlimDeserializer | List.pm, Server::_process | t/list-serializer.t, t/list-deserializer.t, t/list-malformed-input.t, t/server-malformed-input.t, t/server-unicode-frame.t | Frame length uses UTF-8 bytes; item lengths use UTF-16 code units, including two units for supplementary characters. German, Estonian, Sindarin, BMP, supplementary, mixed, and combining text round trips pass. Perl strictly rejects malformed and truncated UTF-8. Java v20251025's new String(bytes, UTF-8) replaces invalid sequences; Perl intentionally chooses strict rejection. Unpaired UTF-16 surrogate behavior is not representable by ordinary Perl Unicode scalars and is not claimed. |
| Recursive list grammar, length fields, separators, and empty list/items | Protocol guide; installed v20251025 SlimDeserializer | List::serialize, List::list, _take_utf16_units | t/list-serializer.t, t/list-deserializer.t, t/list-malformed-input.t, t/server-malformed-input.t | Cursor parsing validates delimiters, decimal counts, separators, item boundaries, close bracket, and trailing data before returning any statements. It enforces depth 128, 1,000,000 total decoded elements per message, and 16,777,216 UTF-16 units per item. These are Perl defensive limits. FitNesse treats an empty or non-[ item as a string and tries recursively parsing a [-prefixed item; if that recursive parse raises SyntaxError, it falls back to the literal string. Perl preserves that fallback. The Java parser consumes a closing ] without checking for trailing characters; Perl rejects trailing data as a defensive policy. |
| Null wire token and scalar distinctions | Protocol guide says null is represented by the four-character string null; installed serializer | List::serialize; StatementExecutor.pm | t/list-serializer.t, t/assign-instruction.t, added t/symbol-null.t | undef serializes to the wire token null; zero and empty string have distinct encodings. Literal string null and null share the wire representation, so type-aware fixture conversion is required to distinguish them. callAndAssign now retains an actual undefined result internally while returning the null token on the wire; exact symbol substitution preserves that null, and embedded substitution uses null text. |
| Instructions: import, make, call, callAndAssign, assign; malformed operations | Protocol guide, “The Instructions” and standard MALFORMED_INSTRUCTION; v20251025 InstructionFactory, InvalidInstruction, ListExecutor | Statement.pm, ListExecutor.pm | t/statement.t, t/instance-creation.t, t/method-invocation.t, t/assign-instruction.t, t/list-executor.t, t/server-malformed-input.t | All five instruction names are implemented. Unknown operations and malformed shapes return __EXCEPTION__:message:<<MALFORMED_INSTRUCTION ...>> and retain the ID when available; a later valid frame on that connection still runs. Malformed-instruction detail is truncated in Perl to keep responses bounded. Java maps unknown operations to InvalidInstruction with the same category; malformed required fields can throw during instruction construction and close the worker connection. |
| Imports, class names, constructors, constructor arguments, and class-symbol chaining | Protocol guide, Import, Make, fixture chaining; installed v20251025 StatementExecutor / SlimExecutionContext | StatementExecutor::add_import, resolve_class, create, replace_symbols, ConverterRegistry::validate_arity | t/instance-creation.t, t/graceful-names.t, t/assign-instruction.t, t/typed-constructor-conversion.t, t/typed-invocation-arity.t, acceptance suite | Graceful and fully qualified names, imports, constructor arguments, and a class argument resolved from a symbol are supported. HTML-table constructor arguments become hashes. Fixtures may declare slim_constructor_signature; scalar constructor arguments use the same converter registry as typed method arguments. A declared parameters array fixes constructor arity, including [] for zero; mismatch is rejected before conversion or construction and does not register the instance. Missing metadata preserves untyped behavior. Java constructor lookup rejects unmatched arity with a class[n] error; Perl reports NO_CONSTRUCTOR class[n] with expected-arity detail. Reflective overload selection remains unavailable. |
| Actor and library resolution/order | Protocol guide, “Library Instances” and “Actors”; installed StatementExecutor / SlimExecutionContext | StatementExecutor::create, instance, call; SlimHelperLibrary.pm | t/library-resolution.t, t/library-fixtures.t, t/library-identity.t, t/library-in-setup.t, t/slim-helper-library.t | Actor methods take priority; otherwise libraries are searched newest-first. Library IDs beginning with library are registered as libraries (not just numeric IDs); duplicate IDs retain their objects and direct lookup uses first registration. Automatic getFixture, pushFixture, and popFixture preserve actor identity. |
| System-under-test delegation | Protocol guide [0.1] System Under Test; FitNesse v20251025 StatementExecutor and SystemUnderTestMethodExecutor | StatementExecutor::_call, _system_under_test | Added t/sut-delegation.t | Verified order is actor method, SUT method, then newest-first libraries. Java selects fields named systemUnderTest or annotated @SystemUnderTest; Perl fixtures may use a blessed-hash field named system_under_test (preferred) or the Java-compatible systemUnderTest. The same resolved arguments, including object-valued symbols, reach the SUT; a thrown SUT error is returned and does not trigger library fallback. Java annotation discovery for arbitrarily named fields has no direct Perl equivalent and is not implemented. |
| Method name translation and invocation | Protocol guide, Call; installed v20251025 NameTranslator, MethodExecutor | StatementExecutor::slim_to_perl_method, StatementExecutor::_call, ConverterRegistry::validate_arity | t/graceful-names.t, t/method-invocation.t, t/library-resolution.t, t/sut-delegation.t, t/typed-invocation-arity.t | Lower-camel SLiM names map to Perl underscore names; actor/SUT/library dispatch is exercised. Explicit parameters metadata enforces exact method arity before conversion/invocation; an empty list means zero arguments, while absent parameter metadata preserves dynamic Perl arity. Mismatches return NO_METHOD_IN_CLASS and do not retry a lower-precedence target. Java overload selection remains unavailable because each Perl dispatched method exposes one signature. Acronym-heavy names, Unicode identifiers, and methods exposed only through dynamic AUTOLOAD behavior lack compatibility evidence. |
| Exact and embedded symbol substitution, references, identity | Protocol guide, [0.2] fixture chaining and [0.3] symbol-as-object; installed VariableStore / SlimSymbol | StatementExecutor::replace_symbols, set_symbol, get_symbol, call_and_assign | t/symbol.t, t/statement-executor.t, t/library-resolution.t, t/server-concurrency.t; added t/symbol-null.t | Exact symbols preserve references and identity; embedded substitution is separate; missing symbols remain unchanged. A null-valued symbol created by callAndAssign now remains null internally. Symbols with names outside Perl's current \w+ pattern (including some Java Unicode/identifier cases) are not fully covered. FitNesse's separate $`expression` table feature is not in the protocol specification and is not evaluated by Perl; see the compatibility investigation. |
| FitNesse table expressions ($`expression` ) | FitNesse Expressions in Tables; Java v20251025 VariableStore, SlimExpressionEvaluator, and SlimTable. Not a requirement in the SLiM protocol specification. | No Perl expression evaluator; StatementExecutor::replace_symbols handles $NAME and preserves backtick expressions as text | Existing symbol tests establish ordinary symbol behavior; the native acceptance page is the focused integration evidence | Java evaluates expressions with Nashorn and Java Map/List objects, both on the SlimService side for arguments and in FitNesse's table client for expected cells. Perl's observed result is the original expression text. This is a known difference in a documented FitNesse feature, not a wire-protocol defect. Reproducing the page requires JavaScript operations, Java conversions, methods, and class identity; no bounded subset is implemented. |
| Standard and metadata-driven argument/result converters | Data Types; FitNesse v20251025 ConverterRegistry, ConverterSupport, GenericEnumConverter, and other converter classes in src/fitnesse/slim/converters | ConverterRegistry.pm, explicit method/constructor and fixture enum metadata | t/custom-converters.t, t/date-character-converters.t, t/double-converter-parity.t, t/enum-converters.t, t/typed-constructor-conversion.t, t/typed-sequence-converters.t, t/query-fixtures.t, acceptance TestCustomConverter | Perl supports declared String, boxed/primitive Boolean, Integer, Long, Double, Character, Date, and fixture-declared enum conversions for method and explicitly typed constructor arguments and results. Boxed scalar blanks map to null, primitive blanks to their Java defaults, and Java integer/long bounds are checked. Double accepts validated Java decimal/hexadecimal forms and special values, preserves signed zero, and formats with a round-trip-checked Java-style representation. Enum matching prefers exact names, then uses locale-independent case-insensitive matching in declaration order; blank input is null, nonblank input is not trimmed, and invalid values produce structured errors. Enum declarations are fixture-local metadata, not reflection or global converter registration. Standard List/Collection behavior, typed arrays and List/Collection, custom converters, null/reference argument passthrough, and HTML-table hashes are retained. Java also registers Map and dynamically constructs converters from reflected types and JavaBeans property editors. Perl does not emulate Java reflection: enum types must be declared explicitly, and other reflective type-derived converters remain unsupported. |
| Return conversion, void, and collections | Protocol guide, Call; installed MethodExecutionResult, VoidConverter, ConverterRegistry | ConverterRegistry::convert_result, StatementExecutor::_call, MapResult.pm | t/custom-converters.t, t/hash-results.t, t/list-serializer.t; acceptance HashTableTests, TestCustomConverter | Declared return converters, runtime fallback for annotated fixtures, nested list conversion, map HTML results, and explicit object string methods are covered. Declared void now emits FitNesse's /__VOID__/ marker; boxed Boolean emits lowercase true/false. Unannotated Perl undef cannot distinguish Java void from an object-returning method that returns null; fixture metadata is needed for exact void semantics. Arbitrary references are deliberately not globally stringified. |
| HTML table arguments and map results | Protocol guide, optional Hashes; Data Types—Maps; installed MapConverter | StatementExecutor::replace_table_with_hash, MapResult.pm | t/table-to-hash.t, t/hash-results.t, t/echo-hash-fixtures.t; acceptance HashTableTests | Two-column HTML tables map to Perl hashes; hash results are rendered as sorted FitNesse table HTML, with recursive values and escaping tests. This is a compatibility conversion rather than a protocol-level map type. HTML parser edge cases and parity for every Java MapConverter representation are not exhaustive. |
| Exception formatting and standard errors | Exception Handling; protocol guide standard exception messages; installed SlimException, MethodExecutionResult, SlimServer | StatementExecutor::exception_result, Statement.pm | t/stop-exceptions.t, t/instance-creation.t, t/method-invocation.t, acceptance TestExceptionIsDisplayed | Ordinary errors, Java-style messages, missing class/instance/method categories, and message:<<...>> are covered. Full stack traces and all constructor/converter error category/message combinations do not match Java reflection in every case. NO_CONSTRUCTOR, invalid parameter overload diagnostics, and timeout markers need additional targeted coverage. |
| Stop-test and stop-suite behavior | Protocol guide, “Aborting a Test”; installed StatementExecutor / ListExecutor | StatementExecutor::exception_result, ListExecutor.pm | t/stop-exceptions.t, opt-in t/stop-page-order.t, acceptance StopTest, StopSuite, StopTestInSuiteSetUp | StopTest/StopSuite markers stop the current instruction batch; the next batch resets the stop flag. Current page-level acceptance tests pass when generated-page state is isolated. Detailed batch/table boundary parity beyond those cases is not exhaustive. |
| Ignore-script / ignore-rest-of-test behavior | Protocol guide “Ignoring Test” / “Ignoring All Tests”; FitNesse v20251025 SlimException, StatementExecutor, and ListExecutor | StatementExecutor::exception_result, call_and_assign, ListExecutor::execute | Added t/ignore-semantics.t | Exceptions whose class-name prefix contains IgnoreScriptTest or IgnoreAllTests produce the exact __EXCEPTION__:IGNORE_SCRIPT_TEST: or __EXCEPTION__:IGNORE_ALL_TESTS: marker, without a message suffix. Both request stopping of the current instruction batch, and ListExecutor resets that stop state at the next batch, matching Java. A thrown fixture exception during callAndAssign does not bind the result symbol. FitNesse interprets the different markers to skip the remaining script table or test across subsequent instruction groups; that client-side policy is outside this Perl server. Method and constructor exceptions, stop behavior, delegated-SUT exceptions, and ordinary messages that merely mention ignore names are covered. |
| Connection/session isolation and failure containment | Protocol guide's repeated-exchange lifecycle; installed v20251025 SlimServer / SlimService | Server::run, process; each client gets a new ListExecutor | t/server-concurrency.t, t/server-malformed-input.t | Real-socket tests show malformed prefix, truncated payload, oversized frame, invalid UTF-8, and structurally corrupt batches close only the affected worker connection; a fresh valid client is served after each. Corrupt request batches are fully parsed before ListExecutor runs, proven with a state-mutating fixture constructor in element one and malformed serialization in element two. Java also deserializes the full outer request before execution but logs worker stack traces for transport/list failures; clean EOF at a frame boundary is an I/O failure in Java but ends the Perl session cleanly per this implementation policy. |
| Protocol version and backward compatibility | Protocol guide v0.4/v0.5; installed SlimVersion and SlimServer | Server::_process receives the transport-selected greeting version | t/server.t, t/pipe-transport.t; individual acceptance suites over v20251025 | Both transports use V0.5, as required by the pinned release. No request-side negotiation or compatibility matrix against older FitNesse releases is tested. Diagnostic tunneling follows the pinned logger where its behavior differs from the prose guide; see the next row. Ignore markers are covered separately above. |
| v0.5 stdin/stdout pipe framing | Protocol guide, “Slim Server”; installed SlimPipeSocket, SlimStreamReader, and SlimServer | Server::run_pipe, process_pipe, shared _process session routine | t/pipe-transport.t; FitNesse v20251025 CLI tests for NamingConvention and SlimSymbolManipulation | Port 1 selects one child session using inherited stdin/stdout. The child receives argument 1; it emits the V0.5 banner and then the same byte-length frames as TCP. A real FitNesse process launched bin/perl-slim with COMMAND_PATTERN=%m and received responses for 5 and 18 assertions respectively. No Perl TCP listener was started for those runs. |
| SUT stdout/stderr isolation and v0.5 tunnel markers | Protocol guide, StdOut and StdErr from the SUT; installed v20251025 SlimPipeSocket and LoggingOutputStream | Server::run_pipe, _execute_with_tunneling, _tunnel_capture; ListExecutor::execute_with | t/pipe-transport.t, t/list-executor-callback.t; isolated six-assertion FitNesse v20251025 pipe test and Java control | Fixture stdout and stderr are captured independently per instruction using disk-backed temporary files. Regular and explicit Perl prints, raw fd writes, and a waited child process cannot corrupt the frame and are forwarded before the batch response. FitNesse reconstructs content under separate stdOut and stdErr execution-log elements. The guide says first lines use SOUT : / SERR : and continuations use dot; the pinned JAR constants and logger instead use SOUT.: / SERR.: first and SOUT : / SERR : for continuations. A one-newline-only message is suppressed by the pinned logger. Records are line-oriented; partial lines are terminated at each instruction output boundary. UTF-8 bytes are preserved. Per-stream order is retained; cross-stream interleaving is not guaranteed because channels are separately spooled. |
FitNesse v20251025 converter inventory
The authoritative registry is ConverterRegistry.java. It registers only void, String, Date, boxed and primitive Double, Long, Integer, Character, and Boolean, plus Map. There is no built-in Float, Short, or Byte converter. getConverterForClass additionally discovers registered superclasses/interfaces, JavaBeans property editors, enums, arrays, and collections at runtime. Those latter features require Java Class/generic type metadata and are not equivalent to adding a fixed alias in the Perl registry.
| Java type / feature | v20251025 input semantics | v20251025 result semantics | Perl status |
|---|---|---|---|
| String | Preserves the input text, including empty text | Preserves text; null uses the SLiM null representation | Supported for declared signatures |
| Boolean / boolean | Case-insensitive true or yes is true; other nonblank text is false. Blank becomes null for boxed Boolean and false for primitive Boolean. | Lowercase true or false; null remains null for boxed Boolean | Supported and covered in t/custom-converters.t |
| Integer / int | Blank becomes null/zero respectively; Integer.valueOf accepts signed base-10 values within -2147483648..2147483647, otherwise reports Can't convert <value> to integer. | Decimal integer text; primitive and boxed values share the same value text | Added. Boundaries, overflow, blank defaults, result text, and invocation-context errors are covered in t/custom-converters.t |
| Long / long | Blank becomes null/zero respectively; Long.valueOf accepts signed base-10 values within -9223372036854775808..9223372036854775807, otherwise reports Can't convert <value> to long. | Decimal long text | Added. Both endpoints, overflow, blank default, and a declared signed argument path are covered in t/custom-converters.t |
| Double / double | Blank becomes null/0.0 respectively. Double.valueOf accepts decimal/exponent forms, optional fFdD suffixes, hexadecimal significands with mandatory binary p exponents, and case-sensitive signed NaN/Infinity. ASCII characters removed by String.trim() are accepted at the ends. | Java Double.toString: NaN, signed Infinity, signed zero, and shortest round-tripping decimal text with Java exponent thresholds | Implemented with strict Java grammar validation and POSIX strtod under the C numeric locale. The formatter searches 1–17 significant digits (at least two in scientific notation) and checks binary64 round trips; it matched 1,000 deterministic finite values against the pinned runtime. Boxed/primitive blank behavior and invocation paths are covered in t/double-converter-parity.t. Exhaustive equivalence over every binary64 value and every libc is not claimed. |
| Character / char | Blank/Java-whitespace-only becomes null or NUL; otherwise String.charAt(0) returns the first UTF-16 code unit | BMP code units become the corresponding character; a lone surrogate becomes ? when UTF-8 encoded, Java's default malformed-surrogate replacement | Added. Perl uses integer code units 0..65535, so supplementary input maps to its high surrogate and isolated surrogates remain representable. t/date-character-converters.t covers BMP edges, supplementary input, isolated surrogates, Java whitespace, declared signatures, and result serialization. |
| Date | SimpleDateFormat("dd-MMM-yyyy", Locale.US) accepts leading Java whitespace and parses from the beginning using the JVM default timezone; it is lenient, accepts full or abbreviated case-insensitive month names, and does not require consuming trailing text. Day overflow rolls across months. Blank becomes null. | Same US-locale dd-MMM-yyyy format in the JVM default timezone | Added. Perl represents Date as epoch milliseconds and uses process-local timelocal/localtime; tests cover default timezone, US month names, leap days, day rollover, blank/malformed values, declared signatures, and years 1583..9999. Pre-1583 Julian/Gregorian cutover behavior, BC/out-of-range years, and some SimpleDateFormat number-token quirks are not claimed. |
| void | Always converts to null | Always emits /__VOID__/ | Supported and covered |
| Map | Parses one HTML table into key/value strings; malformed or non-single-table HTML yields an empty map | Emits FitNesse hash_table HTML, escaping cell content as needed | HTML input is converted to Perl hashes before fixture invocation; hash results use MapResult. Covered by t/table-to-hash.t and t/hash-results.t |
| Enums | Exact enum name first, then case-insensitive name match; unknown name reports the enum type | Enum.name() | Not inferred from Perl metadata. Fixtures can register a converter explicitly |
| Arrays | Strips optional leading [ and trailing ], comma-splits using Java String.split behavior, trims each item, then applies the component converter | Bracketed comma-separated values; non-null items use their runtime converter and nulls use null text | One-dimensional typed arrays are supported for registered scalar component converters. Blank whole input is null; [] is empty. Constructor/method arguments, returns, invalid values, primitive defaults, and identity are covered in t/typed-sequence-converters.t. Multidimensional parsing is not supported |
| Collections | Same list parsing; generic element type selects its converter. List and Collection interfaces default to ArrayList; Java also maps Set to HashSet and Queue to PriorityQueue. | Java collection toString() form with element rendering | Typed List<T> and Collection<T> are supported; untyped List behavior is preserved. Tests cover order, duplicates, null/empty values, results, and symbol identity in t/typed-sequence-converters.t. Set, Queue, concrete reflective collection classes, and nested generics are not implemented |
| JavaBeans property editor | Uses the class-specific registered property editor if available | Uses the editor's getAsText() | No implicit equivalent; explicit custom converter is the supported Perl extension point |
Focused reference execution
The Character and Date implementations were already present before the constructor-path correction in this increment. I executed the converter classes and ConverterSupport.convertArgs from the pinned /opt/fitnesse/fitnesse.jar (manifest version 20251025) with the JVM timezone fixed to UTC. The Java output was:
char_A=0041 supplementary_first=d83d boxed_blank=null
primitive_blank=0000
date_ms=1706745600000 date_text=01-Feb-2024
bad_date=message:<<Can't convert 12-NotAMonth-2024 to date.>>
char_text=Ω surrogate_wire=[91, 48, 48, 48, 48, 48, 49, 58, 48, 48, 48, 48, 48, 49, 58, 63, 58, 93]
This confirms CharConverter uses String.charAt(0) (one UTF-16 code unit), ConverterBase maps blank boxed values to null, PrimitiveCharConverter maps blank input to NUL, and DateConverter uses the lenient default SimpleDateFormat("dd-MMM-yyyy", Locale.US) with the JVM default timezone. Its parse accepts the valid prefix 32-Jan-2024 and formats the normalized Date as 01-Feb-2024. The Perl converter tests exercise the same input/output cases; t/typed-constructor-conversion.t additionally sends a supplementary character, a leap-day Date, and a blank Character through serialized make/call instructions.
The Java constructor reference is StatementExecutor.createInstanceOfConstructor: after selecting a constructor by arity, it invokes ConverterSupport.convertArgs(args, constructor.getParameterTypes()) before newInstance. Perl previously passed constructor arguments directly after symbol/table substitution. It now permits a fixture to declare slim_constructor_signature and converts those values with the existing per-executor registry. This is explicit metadata, not Java reflection or overload selection.
For the array/collection extension I ran /tmp/SlimArrayCollectionReference.java directly against the pinned JAR using Java source-file mode. Java v20251025 produced:
int_array=[1, 2, -3] len=3 formatted=[1, 2, -3]
string_array=[alpha, , beta] len=3 empty=0
boolean_array=[true, true, false]
blank_array=null empty_bracket_len=0 null_array=null
bad_array=message:<<Can't convert bad to integer.>>
list=[3, 1, 3] class=ArrayList empty=null null=null
collection=[4, 2, 4]
set=[1, 3]
queue=[1, 3, 2] iteration=[1, 3, 2]
bad_list=message:<<Can't convert bad to integer.>>
The run confirms bracket syntax, item trimming, trailing-empty removal from Java String.split, boxed blank elements, conversion errors, empty [] versus blank/null top-level input, ordered duplicate-preserving ArrayList results, and Java collection formatting. Set and Queue formatting reflects Java implementation iteration behavior; Perl intentionally does not claim to emulate those collection classes.
Corrections made in this audit
- Expanded v0.4 lengths:
List::listaccepted exactly six digits despite the v0.4 specification allowing longer fields. It now accepts six or more digits.Server::_processalso assumed a seven-byte frame header;_read_messagenow parses the colon-terminated six-or-more digit byte length up to the Java signed-int range._take_utf16_unitsnow advances an index instead of repeatedly deleting the string prefix. Regressions cover a 1,000,000-unit list field and a 1,000,000-byte frame. - Null-valued symbols:
StatementExecutor::callpreviously turned undefined results into the textnullbeforecallAndAssignstored them. The call-and-assign path now stores the original undefined result while returning the wire null token. Exact symbol references preserve null; embedded references rendernulltext. Regression coverage exercises the instruction path. - Library IDs: the protocol defines libraries by the
libraryprefix, but the implementation required a numeric suffix. Registration and lookup now accept any such suffix. A nonnumeric-ID library dispatch test covers it. - Declared result types: the standard boxed Boolean converter lacked
to_string, and the registry had novoidconverter. Boolean results now serialize astrue/false(null stays null), and declared void results use/__VOID__/, matching the installedVoidConverter. Focused converter tests cover these results. - Character and Date conversion: declared
Character/charsignatures now use integer UTF-16 code units, preserving supplementary high surrogates and isolated surrogate values without forcing them into invalid Perl Unicode strings. DeclaredDatesignatures use epoch milliseconds, local-timezone parsing/formatting, US month names, and lenient Gregorian day rollover for years 1583..9999. Focused tests cover argument/result conversion and the supported boundaries; unsupported calendar/parser edges remain explicit. - Constructor conversion path: the Java executor converts constructor arguments using reflected parameter types. Perl previously passed converted symbols/table arguments directly to
new; fixtures with type-sensitive constructors can now declareslim_constructor_signatureand reuse the same converter registry as method calls.t/typed-constructor-conversion.texercises Character, Date, blank-to-null, conversion errors, and no partially registered instance after failure. Untyped fixture constructors remain unchanged. - Typed arrays and collections: Java's
GenericArrayConverterandGenericCollectionConvertercompose a converter for the declared component type. Perl now resolves one-dimensionalT[],java.util.List<T>, andjava.util.Collection<T>metadata into a shared sequence converter. It preserves existing references passed as symbol arguments; for explicitly typed sequence results,callAndAssignretains the raw Perl array reference while formatting the wire response. Untyped result conversion remains unchanged. Tests exercise constructor/method arguments, typed results, invalid values, aliases, null/empty values, string/boolean/integer/Double elements, typed reference identity, and preservation of the untyped path. Set, Queue, nested generic conversion, and reflective concrete collection classes remain unsupported.
Overall assessment and remaining gaps
The Perl service now has direct evidence for FitNesse v0.5 framing over both TCP and child-process pipes, recursive lists, byte/code-unit length separation, all five instruction forms, the implemented fixture-resolution path, common symbol behavior, selected conversions, exception/stop handling, forked TCP session isolation, and diagnostic output reconstruction. The pipe mode was exercised with raw child-process tests, two existing acceptance pages, and an isolated stdout/stderr reconstruction page compared with the pinned Java service. Current CI and the 45/50 TCP acceptance baseline provide additional integration evidence. This is partial, tested compatibility, not complete SLiM protocol conformance.
Confirmed remaining gaps include exact Java Double edge parsing/formatting; pre-1583 and out-of-range Date behavior and some SimpleDateFormat parsing quirks; Set/Queue, nested generic, multidimensional array, enum, JavaBeans property-editor, and reflection-derived concrete collection behavior; Java overload selection for constructors; Java annotation discovery for arbitrary SUT field names; broader constructor/method reflection diagnostics; and FitNesse's separately documented $`expression` table feature, whose v20251025 Java service uses Nashorn and Java object semantics. The expression feature is not part of the SLiM protocol and is not implemented by the Perl core. Pipe-mode stdout/stderr tunneling is implemented against the pinned FitNesse behavior, which reverses the first/continuation prefix assignments in the published guide; exact cross-stream ordering is not guaranteed. Ignore marker emission, server-side batch stopping, and SUT delegation through the explicit Perl fixture field are now covered. Other unverified edges are listed in the matrix. The 50-page TCP acceptance baseline remains unchanged, and no upstream acceptance expectation was modified. See slim-pipe-transport.md for pipe invocation and lifecycle details.