FitNesse SLiM acceptance baseline

The two ErrorSituationsDuringSlimServerStartup failures were reproduced twice each. B_TimeoutAsSlimServerDiesBeforeConnecting has 2 right and 1 wrong: the sole failure expects the dead-runner diagnostic, but the nested request inherits FitNesse manually.start.test.runner=true and connects to the existing Perl listener at 127.0.0.1:8099 (execution log: connection to running SlimService, exit 0, 0 seconds). C_TimeoutAsNoServerAtGivenSocket has 3 right and 1 wrong: its sole failure expects connection refusal at 8099, but the Perl listener is bound there and the nested debug request succeeds (exit 0, 0 seconds); its elapsed-time regex passes. Both pages' HTTP and execution-log assertions pass. The failure boundary is the shared FitNesse harness configuration and occupied test port, rather than the Perl implementation. Faithful coverage requires an isolated FitNesse context with the outer Perl service on another port, 8099 unbound for C, and B's nested request configured to launch its invalid runner. The upstream pages and their assertions remain unchanged.

Updated 2026-10-16 on codex/complete-slim, after custom converter support and a full 50-page rerun. The installed FitNesse distribution is v20251025.

FitNesse ran all 50 pages against the external Perl SLiM server at 127.0.0.1:8099. Every response identified fitnesse.slim.SlimService and the external connection; all returned HTTP 200. The listener remained alive throughout the inventory, with zero unexpected server exits.

Perl tests

bin/test-dev: PASS, 41 files and 426 tests. Four known TODO-marked failures remain (three boilerplate checks and one Unicode-return case). The optional live stop-page order regression adds 43 passing assertions when run with RUN_STOP_PAGE_INTEGRATION=1.

PageDriver behavior implemented

The installed Java class has an implicit no-argument constructor. The acceptance scenarios use createPageWithContent(String,String), makeATestPage(String), makeASuitePage(String), requestPage(String), content(), contentContains(String), contentMatches(String), and lineNumberContaining(String). The class metadata in the installed jar confirms this method surface; the upstream implementation is available in the FitNesse source repository.

The Perl fixture keeps this test-harness behavior under t/lib/Fitnesse/Fixtures/, outside lib/Test/FitNesse/Slim/. It creates a page with POST /<page> using responder=saveData, editTime=9999999999999, ticketId=321, and pageContent; marks it as a test page with responder=saveProperties and PageType=Test; and requests the supplied URI with HTTP GET. This supports page views and the ?test, ?test&debug, ?suite, and ?executionLog responders used by the scenario library. The Java requestPage method returns the response status, and the Perl implementation retains that status and body on the PageDriver instance. content, content_contains, content_matches, and line_number_containing inspect that retained response. HTTP error statuses remain available with their response body; transport failures are raised as fixture exceptions and retained in the shared ResponseContext for later fixture inspection.

The response context is process-local. The external Perl server forks one worker per SLiM connection, so a nested FitNesse request can open its own connection while the outer PageDriver call waits; each connection keeps its own fixture state.

RUN_PAGE_DRIVER_INTEGRATION=1 perl t/page-driver-integration.t passed against the live FitNesse server and Perl SLiM listener. It created and marked a generated test page, FitNesse executed that page over a second Perl SLiM connection, and the original outer connection then successfully executed another fixture statement. The upstream ScenarioLibraryTestSuite.ScenarioLibraryShouldIncludeVariables page also passed separately (6 right, 0 wrong, 0 ignored, 0 exceptions) through the Perl server; it is not one of the 50 Test=true inventory pages. Focused HTTP request, status, encoding, response-retention, and error tests are in t/page-driver.t (25 assertions).

The acceptance inventory now removes the generated MySuite subtree before each Test=true page request by POSTing confirmed=yes to /MySuite?deletePage. This is required because FitNesse retains pages created by PageDriver across HTTP test requests. The StopTestInSuiteSetUp page creates MySuite.SuiteSetUp; leaving it in the wiki makes later stop pages inherit its stop-test behavior. The reset is limited to generated test data and does not change the upstream wiki sources.

The Perl ResponseExaminer now reads the current ResponseContext created by PageDriver. It implements response status and body access, raw and escaped content selectors, rendered response HTML, line access, multiline/dotall regex matching, match counts, and configured capture extraction. The acceptance scenario also calls extractMatch(pattern, type, group), which selects a source and capture in one operation. Unknown source types, invalid patterns, and a missing response raise fixture errors; a valid pattern without a match returns null, matching upstream found behavior. The focused cases include nested failure, ignored markup, and exception-detail extraction.

Automatic SlimHelperLibrary actor stack implemented

The installed fitnesse.slim.SlimHelperLibrary exposes getFixture(), pushFixture(), and popFixture() using the executor instance named scriptTableActor. The Perl StatementExecutor creates one helper for each executor and registers it as the oldest library. Actor methods still take precedence, and user libraries remain newer. The helper stores the fixture references directly on its own stack; nested pushes restore in LIFO order, and each executor has an independent stack. A missing actor reports NO_INSTANCE, and popping an empty stack reports EmptyStackException.

The upstream TestSlim.toString() output is needed when getFixture() returns an actor to a FitNesse cell. The Perl TestSlim fixture now stringifies as TestSlim: <constructorArg>, <stringArg> and explicitly preserves identity equality, so serializing a helper result does not change symbol or fixture identity behavior. This behavior is scoped to the fixture rather than adding arbitrary object stringification to protocol serialization. See the upstream SlimHelperLibrary.java and TestSlim.java.

SplitFixture and DuplicateRows behavior implemented

The installed FitNesse v20251025 classes expose a one-string constructor and query() method. SplitFixture uses its constructor text as row data: semicolons separate rows, commas separate cells, and query results are nested lists of column/value pairs (column keys are the strings 1, 2, and so on). DuplicateRows returns one-column x rows in the phase A or B order shown by the upstream acceptance tables. It deliberately retains repeated SuiteSetUp and SuiteTearDown values because duplicate multiplicity is what the ordered and subset query cases exercise. The Perl fixtures live in t/lib/Fitnesse/Fixtures/, outside the SLiM executor.

QueryTableSuite.SetContextValue uses the SLiM assign instruction to transfer query cells into symbols. The pinned FitNesse v20251025 session uses protocol V0.5; the server stores the supplied value and returns OK, while null remains an undefined symbol value. The class metadata in the installed FitNesse JAR confirms SplitFixture(String), DuplicateRows(String), and query(); the unchanged acceptance pages validate the row structures, ordering, duplicate handling, matching, and symbol assignment end to end.

Stop and exception behavior implemented

The installed FitNesse v20251025 implementations of ConstructorThrows, DecisionTableExecuteThrows, and the SLiM exception path were used as the behavioral reference. The Perl ConstructorThrows fixture raises ordinary, StopTest, and StopSuite constructor exceptions. DecisionTableExecuteThrows reproduces the upstream state, method errors, and table hooks. TestSlim now exposes the exception methods used by the acceptance page. The statement executor formats ordinary and Java-style exception payloads, and returns the ABORT_SLIM_TEST or ABORT_SLIM_SUITE marker for stopping exceptions. The list executor stops the remainder of the current instruction batch and resets the stop request before the next batch. FitNesse then applies its own page/suite rules, including running the appropriate teardown.

StopTestInSuiteSetUp and TestExceptionIsDisplayed pass their outer checks. In an isolated wiki, StopSuite and StopTest also pass: their nested output includes the expected teardown results, while the stop markers still skip the intended tests. The order-sensitive discrepancy came from reusing FitNesse's generated MySuite pages across separate inventory requests, not from stop-state handling in the Perl executor. IgnoredTestsShouldColorGray verifies the nested stop exception and that later script statements are not run. Two of its outer checks remain incompatible with the installed renderer: they expect class="ignore">Not Executed..., while FitNesse v20251025 emits class="slimRowColor3"><td>Not Executed... <span class="ignore">Test not run</span>.

Hash/map result conversion implemented

The installed FitNesse v20251025 HashFixture.hash() returns a sorted TreeMap. MethodExecutionResult converts that map with MapConverter before SlimSerializer serializes the SLiM response. The converter emits table.hash_table rows with hash_key and hash_value cells, escapes ordinary text, and retains valid HTML cell content.

The Perl engine previously returned a plain hash reference from StatementExecutor::call. Since Test::FitNesse::Slim::List serializes arrays recursively but does not encode hashes, the reference reached the wire as HASH(0x...). Test::FitNesse::Slim::MapResult now converts only plain hash-reference method results into the upstream table form before response serialization. It sorts keys, handles nulls and nested map/list values, and uses explicit object string methods inside maps; other executor references and list behavior remain unchanged. The regression first failed with HASH(0x6401465af600) and now checks the full output and identity preservation in t/hash-results.t.

Current acceptance results

The full page-level XML inventory completed all 50 pages through the external Perl service. The runner deleted generated MySuite pages before each test request, so results do not depend on prior page order. Using each response's primary <result><counts> element, it reports 45 currently conformant pages and 5 non-conformant pages: 4 FitNesse harness/rendering limitations and 1 environment-limited page. Raw totals are 373 right, 7 wrong, 2 ignored, and 0 exceptions. TestCustomConverter is conformant (13 right, 0 wrong, 0 ignored, 0 exceptions). The listener had zero unexpected exits, and every execution log confirmed the external fitnesse.slim.SlimService connection at 127.0.0.1:8099.

TestCustomComparators reports 1 right, 2 wrong, 0 ignored, and 0 exceptions through Perl. Its two failed outer checks expect the nested response to contain t*t matches test and 2 right, 0 wrong, 0 ignored, 0 exceptions; the nested table instead renders test with expected [glob:t*t]. An equivalent Java-backed run on FitNesse v20251025 passes 3 right, 0 wrong and its execution log records the Java SlimService plus Loaded custom comparator glob: ...GlobComparator. This establishes that the comparator and expectation work in the pinned release when run in its configured context.

This is a shared PageDriver context-fidelity limitation, also present in the custom rendering and ignored-row pages below. Java PageDriver uses its active FitnesseFixtureContext.context for page creation and nested requests; the Perl adapter sends independent HTTP requests to the main FitNesse server. The nested request therefore lacks the per-test comparator registration. This is not a Perl SLiM or fixture defect. A correct fix needs a context-aware FitNesse-side nested-request bridge; globally registering glob or adding a page-specific exception would bypass the behavior under test. See fitnesse-rendering-comparator-compatibility.md for the matched Java/Perl evidence and the architectural boundary.

bin/test-fitnesse reproduces this inventory in a fresh or existing development environment. It compares each page with this committed 45/50 baseline, permits the five known non-conformant pages, and fails on missing execution, regression, server failure, or an execution log that does not prove use of the configured Perl service. Each transport writes separate detailed JSON and service logs under ignored .runtime/.

The 13 fixture pages listed in the preceding phase remain newly conformant, and TestCustomConverter adds one more. The clean-state rerun also confirms StopSuite and StopTest as conformant; the other 48 page statuses match the previous inventory. Page-by-page raw counts and status changes are recorded in the JSON inventory.

Converter support is implemented with explicit fixture method signatures and an executor-local registry. The registry applies declared parameter converters to scalar arguments, declared result converters to return values, and runtime-class converter lookup when the declared result type has no converter. Exact List results that are already arrays stay nested SLiM lists. The TestCustomConverter page verifies standard and custom list rendering, argument parsing, List bypass, and converter restoration without page-specific output strings.

StopTest and StopSuite order dependence

The exact wrong assertions in a contaminated wiki are StopTest's checks that the nested response contains a passing per-test teardown row for MySuite.TestPageA and MySuite.TestPageB, and StopSuite's check for the passing teardown row for MySuite.TestPageA. In each case the stale setup makes FitNesse mark the per-test teardown row as ignored. That is why the prior isolated reruns reported StopTest at 4 right / 2 wrong and StopSuite at 5 right / 1 wrong.

StopTestInSuiteSetUp creates MySuite.SuiteSetUp with a StopTest constructor and does not delete it. When that page runs first, subsequent StopTest and StopSuite requests reuse the same generated wiki subtree and inherit the setup, reproducing exactly those counts. The earlier 50-page inventory ran StopSuite and StopTest before StopTestInSuiteSetUp; its 6/0 results were correct for that order, but the inventory procedure did not reset shared wiki state, so those results were not reproducible by later isolated requests. The same contaminated behavior reproduced on source snapshot dc7561e; the Perl SLiM stop implementation was not the cause. No engine code change was needed.

The opt-in regression RUN_STOP_PAGE_INTEGRATION=1 prove -v t/stop-page-order.t exercises both states: it first confirms the contaminated 4/2 and 5/1 results, then deletes generated MySuite data between requests and verifies StopSuite 6/0, StopTest 6/0 on two consecutive runs, StopTestInSuiteSetUp 7/0, and the reverse StopSuite-then-StopTest order at 6/0 each. The complete 50-page inventory uses the same cleanup before every page.

The original 14 fixture-gap pages grouped into AddUpChange (2 pages), library setup and reused library IDs (2), EchoFixture/HashFixture (hash HTML and include pages, 2), Sleep (2), and five one-page fixture families: EchoScript, TestSlim concatenation, LoginDialogDriver, DetailedDiff, and TableTableIncFirstCol. AddUpChange ranked first because one shared fixture removed a large cascade of failed setup calls; the other two-page groups followed by coverage. The standalone fixtures each covered one page. TableTable required a separate but bounded StatementExecutorConsumer engine increment; it and its fixture now pass. TestSlimWithConverter and the required type conversion support are now implemented.

The fixture-managed-symbol executor path mirrors upstream StatementExecutorConsumer: fixtures receive the current executor, can register a method-name regex for symbol handling, and restore normal replacement when finished. The TableTable page uses the existing TestSlim.echoInt method; it does not need a separate TestQuery fixture.

Per-page results

| Page | Right | Wrong | Ignored | Exceptions | Conformance | Current root cause | |---|---:|---:|---:|---:|---|---| | BaseLineDecisionTable.ComparatorsAreSupported | 10 | 0 | 0 | 0 | conformant | The acceptance page completed with no wrong assertions or exceptions through the external Perl SLiM server. | | BaseLineDecisionTable.MakingTheBaselineTableTheDefaultDecisionTable | 7 | 0 | 0 | 0 | conformant | The acceptance page completed with no wrong assertions or exceptions through the external Perl SLiM server. | | BaseLineDecisionTable.ScenariosAreSupported | 10 | 0 | 0 | 0 | conformant | The acceptance page completed with no wrong assertions or exceptions through the external Perl SLiM server. | | BaseLineDecisionTable.TestFailuresAreMarked | 8 | 0 | 0 | 0 | conformant | ResponseExaminer extracts nested assertion totals and rendered cells; every outer assertion passes. The embedded target page intentionally contains two wrong and two ignored checks. | | BaseLineDecisionTable | 17 | 0 | 0 | 0 | conformant | The acceptance page completed with no wrong assertions or exceptions through the external Perl SLiM server. | | ChainWithInstanceTest | 7 | 0 | 0 | 0 | conformant | The page passes with automatic getFixture, pushFixture, and popFixture support. | | CustomTableRenderingAddedByPluginTest | 5 | 1 | 0 | 0 | harness_limited | Perl's sole wrong outer assertion expects table class="myUseCase"; the nested response reports Could not find class MyUseCase. Java on the pinned JAR passes 6/0 and logs the plugin registration. The Perl HTTP PageDriver request does not use the active context containing the alias and rendering decorator. This is a FitNesse harness-context limitation, not a Perl protocol/fixture defect. | | DefineAliasInPluginTest | 2 | 0 | 0 | 0 | conformant | The acceptance page completed with no wrong assertions or exceptions through the external Perl SLiM server. | | DefineAliasTest | 22 | 0 | 0 | 0 | conformant | AddUpChange reproduces the upstream coin state and dollar/cents results; prior missing-fixture exceptions cascaded through alias examples. | | HashTableTests.HashTableWithHtmlContent | 1 | 0 | 0 | 0 | conformant | HashFixture.hash_is and EchoFixture.echo_and_log reproduce the upstream stored-value return and output behavior; nested HTML content passes unchanged. | | HashTableTests.ShouldReturnHashTable | 1 | 0 | 0 | 0 | conformant | MapResult converts the returned map to sorted FitNesse hash-table HTML before SLiM list serialization. | | HybridDecisionTable.ImplementDynamicDecisionTableWithHybridTable | 5 | 0 | 0 | 0 | conformant | AddUpChange supports the upstream hybrid getter/setter calls and stateful coin totals. | | IgnoredTestsShouldColorGray | 5 | 2 | 0 | 0 | harness_limited | The two outer checks expect ignored scenario cells with class="ignore". Java on the pinned JAR passes 7/0 and emits that markup. Perl's nested HTTP response instead has ordinary unexecuted script rows with slimRowColor3/slimRowColor5 and the Test not run span; part of the nested scenario setup is not interpreted in the same active FitNesse context. The previous obsolete-renderer explanation was incorrect. Stop/ignore behavior itself remains separately covered. | | LibrarySuite.HowToAddFixturesWithParametersToTheLibrary | 5 | 0 | 0 | 0 | conformant | The page passes with automatic getFixture, pushFixture, and popFixture support. | | LibrarySuite.LastLibraryPreceedsEarlierLibraryTest | 2 | 0 | 0 | 0 | conformant | The acceptance page completed with no wrong assertions or exceptions through the external Perl SLiM server. | | LibrarySuite.LibraryInSetUpSuite.LibraryInSetUpAndPageTest | 2 | 0 | 0 | 0 | conformant | The registry retains both objects when setup and page libraries reuse library1; fallback dispatch finds the page library first and the setup library when its method is absent from the page library. | | LibrarySuite.LibraryInSetUpSuite.LibraryInSetUpTest | 1 | 0 | 0 | 0 | conformant | The new LibraryInSetUp fixture tracks delegated echo calls on its own instance. | | LibrarySuite.OneLibraryTest | 1 | 0 | 0 | 0 | conformant | The acceptance page completed with no wrong assertions or exceptions through the external Perl SLiM server. | | QueryTableSuite.SetContextValue | 4 | 0 | 2 | 0 | conformant | All page assertions pass. SetContextValue also reports two ignored symbol-assignment cells, which are expected query-table behavior. | | QueryTableSuite.TestMatching | 27 | 0 | 0 | 0 | conformant | All page assertions pass. SetContextValue also reports two ignored symbol-assignment cells, which are expected query-table behavior. | | ScenarioTestSuite.ScenarioWithOutputParametersInDecisionTables | 6 | 0 | 0 | 0 | conformant | The acceptance page completed with no wrong assertions or exceptions through the external Perl SLiM server. | | ScenarioTestSuite.ScenarioWithOutputParametersInScripts | 4 | 0 | 0 | 0 | conformant | The acceptance page completed with no wrong assertions or exceptions through the external Perl SLiM server. | | ScenarioTestSuite.ScriptSubclassUsedForDecisionTable | 2 | 0 | 0 | 0 | conformant | The acceptance page completed with no wrong assertions or exceptions through the external Perl SLiM server. | | ScenarioTestSuite.UsingConstructorArgumentsFromDecisionTablesInScenarios | 8 | 0 | 0 | 0 | conformant | The acceptance page completed with no wrong assertions or exceptions through the external Perl SLiM server. | | SegregationOfRequirementsAndImplementation | 12 | 0 | 0 | 0 | conformant | The acceptance page completed with no wrong assertions or exceptions through the external Perl SLiM server. | | SlimSymbols.NamingConvention | 5 | 0 | 0 | 0 | conformant | The acceptance page completed with no wrong assertions or exceptions through the external Perl SLiM server. | | SlimSymbols.SlimSymbolManipulation | 18 | 0 | 0 | 0 | conformant | The acceptance page completed with no wrong assertions or exceptions through the external Perl SLiM server. | | StopSuite | 6 | 0 | 0 | 0 | conformant | With generated MySuite state cleared before this page, the nested StopSuite result skips later tests and passes the expected per-test and suite teardown checks. | | StopTest | 6 | 0 | 0 | 0 | conformant | With generated MySuite state cleared before this page, the nested StopTest result skips the current test's remaining statements, continues to the next test, and passes both teardown checks. | | StopTestInSuiteSetUp | 7 | 0 | 0 | 0 | conformant | The stop exception is returned with ABORT_SLIM_TEST; FitNesse skips suite tests and still executes suite teardown. | | TableTableSuite.SymbolsInTableTableManagedByTheFixture | 15 | 0 | 0 | 0 | conformant | The TableTableIncFirstCol consumer fixture increments values, assigns symbols inside the table, and restores normal symbol substitution afterward. | | TestChineseWords | 1 | 0 | 0 | 0 | conformant | EchoScript echoes the original Unicode value and writes its UTF-8 bytes plus newline to stdout. | | TestComparators | 26 | 0 | 0 | 0 | conformant | The acceptance page completed with no wrong assertions or exceptions through the external Perl SLiM server. | | TestCustomComparators | 1 | 2 | 0 | 0 | harness_limited | The generated nested TestPage is executed through the Perl HTTP PageDriver against the main FitNesse server, which does not share the FitnesseFixtureContext configured by upstream SetUp; thus glob: is treated literally in both nested checks. The echo fixture and SLiM values are correct. | | TestCustomConverter | 13 | 0 | 0 | 0 | conformant | Executor-local converter registration and fixture method type metadata reproduce upstream argument parsing and return-value conversion. | | TestDetailedDiff | 22 | 0 | 0 | 0 | conformant | DetailedDiff supplies the expected values; ResponseExaminer confirms the nested page's intentional 25-pass, 50-fail, 1-ignored result and rendered diff markup. | | TestExceptionIsDisplayed | 19 | 0 | 0 | 0 | conformant | The Perl exception fixtures and TestSlim methods now reproduce the expected upstream exception messages, classes, and stop behavior; all 19 outer checks pass. | | TestOrderedQueryWithDuplicateRows | 14 | 0 | 0 | 0 | conformant | All page assertions pass. SetContextValue also reports two ignored symbol-assignment cells, which are expected query-table behavior. | | TestPageWithInclude | 1 | 0 | 0 | 0 | conformant | EchoFixture stores and returns the name set by the included page. | | TestScriptTable | 2 | 0 | 0 | 0 | conformant | The acceptance page completed with no wrong assertions or exceptions through the external Perl SLiM server. | | TestScriptTableDisplayParameterValue | 3 | 0 | 0 | 0 | conformant | The acceptance page completed with no wrong assertions or exceptions through the external Perl SLiM server. | | TestSequentialArgumentProcessing | 8 | 0 | 0 | 0 | conformant | TestSlim concatenates the three supplied values with spaces; the full upstream page validates sequential and mixed argument processing. | | TestSubsetQuery | 11 | 0 | 0 | 0 | conformant | All page assertions pass. SetContextValue also reports two ignored symbol-assignment cells, which are expected query-table behavior. | | TestSystemSlimSuite.ErrorSituationsDuringSlimServerStartup.B_TimeoutAsSlimServerDiesBeforeConnecting | 2 | 1 | 0 | 0 | harness_limited | PageDriver retrieves a 200 response, but the body does not contain the expected dead-test-runner message; the child test runner setup behavior remains unverified. | | TestSystemSlimSuite.ErrorSituationsDuringSlimServerStartup.C_TimeoutAsNoServerAtGivenSocket | 3 | 1 | 0 | 0 | environment_limited | The test expects port 8099 unbound, but the inventory server occupies it; content_matches and PageDriver support are also missing. | | TestSystemSlimSuite.HowtoEnforceA_TimelimitPerTestCommand.NotWorking_SettingSlimTimeout | 1 | 0 | 0 | 0 | conformant | Sleep returned WakeUp 12000 as shown by this page documenting that slim.timeout alone does not stop the command. | | TestSystemSlimSuite.HowtoEnforceA_TimelimitPerTestCommand.PartiallyWorking_SettingSlimFlags | 9 | 0 | 0 | 0 | conformant | Sleep responses match the page's expected table values. Runner startup flags are not independently assessed by this directly connected external-service test. | | TestSystemSlimSuite.TimeoutInSlimTestSystem | 4 | 0 | 0 | 0 | conformant | The acceptance page completed with no wrong assertions or exceptions through the external Perl SLiM server. | | TestTryingParameterizationIsntTooGreedy | 1 | 0 | 0 | 0 | conformant | LoginDialogDriver accepts the shared username/password scenario and records the upstream message and attempt count. | | TestTwoIdenticalTablesOnPageDontConflict | 1 | 0 | 0 | 0 | conformant | The acceptance page completed with no wrong assertions or exceptions through the external Perl SLiM server. |

Next increment

The stop pages are now repeatable when each acceptance request starts with a clean generated MySuite subtree. Four harness/rendering pages and one environment-limited startup page remain non-conformant.