fix(stdlib): TStringList.Text/LoadFromFile preserve verbatim lines

SplitIntoList — the sole backend for TStringList.SetText and LoadFromFile —
trimmed leading and trailing spaces from each split segment. That is correct
for CSV-style splitting but wrong for line-splitting: a TStringList must store
each line verbatim, including indentation.

Removed the trimming so each segment is added as Copy(S, Start, Len) untouched.
SplitIntoList has exactly one caller (SetText), so no trimming-dependent code
is affected.

This also fixes a latent test-infrastructure bug: the threaded test runner
parses each subprocess suite's stdout via TStringList.Text and identifies
failure-detail lines by their two-space indentation. With indentation stripped,
those lines never matched, AddFailure was never called, and the merged result
reported zero failures — so the plain summary printed OK even when [Threaded]
suites failed. With lines preserved, the summary now correctly reports failures.

Regression test: TestRun_TextSet_PreservesLeadingWhitespace asserts on raw
stdout so the spaces inside [] are checked without round-tripping through Text.

Self-hosting fixpoint verified clean.
This commit is contained in:
Graeme Geldenhuys 2026-06-03 00:40:00 +01:00
parent 99b7761bf4
commit 576f60d807
2 changed files with 49 additions and 15 deletions

View file

@ -39,6 +39,7 @@ type
procedure TestRun_TextGet;
procedure TestRun_TextGet_ManyLines;
procedure TestRun_TextSet;
procedure TestRun_TextSet_PreservesLeadingWhitespace;
procedure TestRun_GrowBeyondInitialCapacity;
procedure TestRun_Sorted_Add_OrderedOutput;
procedure TestRun_Sorted_Find;
@ -278,6 +279,31 @@ const
end.
''';
{ Text/LoadFromFile must preserve each line verbatim, including leading and
trailing whitespace standard TStringList semantics. Regression for the
SplitIntoList trimming bug that silently dropped indentation (which in turn
masked failures in the threaded test-runner's subprocess output parser).
The program prints each line wrapped in [] so the harness can see the
surrounding spaces without itself round-tripping through Text. }
SrcTextSetPreservesWhitespace =
'''
program P;
uses Classes;
var L: TStringList; I: Integer;
begin
L := TStringList.Create;
L.Text := 'a' + #10 + ' indented' + #10 + 'trailing ';
WriteLn(L.Count);
I := 0;
while I < L.Count do
begin
WriteLn('[' + L.Get(I) + ']');
I := I + 1
end;
L.Free
end.
''';
SrcGrow =
'''
program P;
@ -694,6 +720,22 @@ begin
end
end;
procedure TE2ETStringListTests.TestRun_TextSet_PreservesLeadingWhitespace;
var
Output: string;
RCode: Integer;
begin
if not ToolchainAvailable then begin Fail('<toolchain-missing>'); Exit end;
AssertTrue('compile+run',
CompileAndRunWithRTL(SrcTextSetPreservesWhitespace, Output, RCode));
AssertEquals('exit 0', 0, RCode);
{ Assert on the raw stdout so the leading/trailing spaces inside [] are
checked directly, without round-tripping through Text. }
AssertEquals('verbatim lines preserved',
'3' + #10 + '[a]' + #10 + '[ indented]' + #10 + '[trailing ]' + #10,
Output);
end;
procedure TE2ETStringListTests.TestRun_GrowBeyondInitialCapacity;
var
Output: string;

View file

@ -273,9 +273,12 @@ procedure SplitIntoList(const S: string; ASep: Integer; AList: TStringList);
var
I: Integer;
Start: Integer;
SLo: Integer;
SHi: Integer;
begin
{ Line-splitter for TStringList.Text / LoadFromFile. Each segment between
separators is added verbatim leading and trailing whitespace is part of
the line and must be preserved (standard TStringList semantics). Trimming
here previously stripped indentation, which silently corrupted any
whitespace-significant text round-tripped through Text. }
AList.Clear;
Start := 0;
I := 0;
@ -283,24 +286,13 @@ begin
begin
if OrdAt(S, I) = ASep then
begin
{ Trim surrounding spaces }
SLo := Start;
SHi := I - 1;
while (SLo <= SHi) and (OrdAt(S, SLo) = 32) do SLo := SLo + 1;
while (SHi >= SLo) and (OrdAt(S, SHi) = 32) do SHi := SHi - 1;
AList.Add(Copy(S, SLo, SHi - SLo + 1));
AList.Add(Copy(S, Start, I - Start));
Start := I + 1;
end;
I := I + 1;
end;
if Start < Length(S) then
begin
SLo := Start;
SHi := Length(S) - 1;
while (SLo <= SHi) and (OrdAt(S, SLo) = 32) do SLo := SLo + 1;
while (SHi >= SLo) and (OrdAt(S, SHi) = 32) do SHi := SHi - 1;
AList.Add(Copy(S, SLo, SHi - SLo + 1));
end;
AList.Add(Copy(S, Start, Length(S) - Start));
end;