Просмотр исходного кода

Update fork and align with head

Curtis Hamilton 3 недель назад
Родитель
Сommit
ead3c10421

+ 2 - 1
packages/fcl-base/examples/README.txt

@@ -81,4 +81,5 @@ csvbom.pp    Test/Demo for BOM detection in CSV document. (needs databom.txt)
 testappexit.pp Test/Demo for TApplication exit code handling. (ExitCode and ExceptionExitcode)
 demoio.pp    Demo for AssignStream from streamio unit.
 testthreadpool  Demo for fpthreadpool unit.
-demolg       TLockGuard demo.
+demolg       TLockGuard demo.
+demo_spinlock TSpinLock demo.

+ 250 - 0
packages/fcl-base/examples/demo_spinlock.pp

@@ -0,0 +1,250 @@
+program demo_spinlock;
+
+{$mode objfpc}{$H+}
+
+uses
+  {$IFDEF UNIX}cthreads,{$ENDIF}
+  Classes, SysUtils, syncobjs, dateutils;
+
+const
+  THREAD_COUNT = 10;
+  INCREMENTS_PER_THREAD = 1000000; // We're supposed to go fast, so lots of increments :-)
+
+{
+  Results on my i9 machine, 24 cores: 
+  Linux: spinlock is ~4 times faster than criticalsection
+  Windows: spinlock is ~10 times faster than criticalsection
+}
+
+var
+  MySpinLock: TSpinLock;
+  CS: TCriticalSection;
+  Counter: Integer;
+  
+type
+  TSpinLockThread = class(TThread)
+    procedure Execute; override;
+  end;
+
+  TSpinLockRelaxedThread = class(TThread)
+    procedure Execute; override;
+  end;
+
+  TCriticalSectionThread = class(TThread)
+    procedure Execute; override;
+  end;
+
+procedure TSpinLockThread.Execute;
+var
+  I: Integer;
+begin
+  for I:=1 to INCREMENTS_PER_THREAD do
+    begin
+    MySpinLock.Enter;
+    try
+      Inc(Counter);
+    finally
+      MySpinLock.Exit;
+    end;
+    end;
+end;
+
+procedure TSpinLockRelaxedThread.Execute;
+var
+  I: Integer;
+begin
+  for I:=1 to INCREMENTS_PER_THREAD do
+    begin
+    MySpinLock.Enter;
+    try
+      Inc(Counter);
+    finally
+      MySpinLock.Exit(False);
+    end;
+    end;
+end;
+
+procedure TCriticalSectionThread.Execute;
+var
+  I: Integer;
+begin
+  for I:=1 to INCREMENTS_PER_THREAD do
+    begin
+    CS.Enter;
+    try
+      Inc(Counter);
+    finally
+      CS.Leave;
+    end;
+    end;
+end;
+
+function RunSpinLockBenchmark: Int64;
+var
+  Threads: array of TSpinLockThread;
+  I: Integer;
+  StartTime, EndTime: TDateTime;
+begin
+  WriteLn('--- Testing TSpinLock Performance (PublishNow=True) ---');
+  MySpinLock:=TSpinLock.Create(False);
+  Counter:=0;
+  SetLength(Threads, THREAD_COUNT);
+
+  WriteLn('Starting ', THREAD_COUNT, ' threads, ', INCREMENTS_PER_THREAD, ' ops each.');
+  StartTime:=Now;
+
+  for I:=0 to THREAD_COUNT - 1 do
+    begin
+    Threads[I]:=TSpinLockThread.Create(True);
+    Threads[I].FreeOnTerminate:=False;
+    Threads[I].Start;
+    end;
+
+  for I:=0 to THREAD_COUNT - 1 do
+    begin
+    Threads[I].WaitFor;
+    Threads[I].Free;
+    end;
+
+  EndTime:=Now;
+  Result:=MilliSecondsBetween(EndTime, StartTime);
+  WriteLn('TSpinLock (Strict) Time: ', Result, ' ms');
+  WriteLn('Counter: ', Counter);
+  WriteLn;
+end;
+
+function RunSpinLockRelaxedBenchmark: Int64;
+var
+  Threads: array of TSpinLockRelaxedThread;
+  I: Integer;
+  StartTime, EndTime: TDateTime;
+begin
+  WriteLn('--- Testing TSpinLock Performance (PublishNow=False) ---');
+  MySpinLock:=TSpinLock.Create(False);
+  Counter:=0;
+  SetLength(Threads, THREAD_COUNT);
+
+  WriteLn('Starting ', THREAD_COUNT, ' threads, ', INCREMENTS_PER_THREAD, ' ops each.');
+  StartTime:=Now;
+
+  for I:=0 to THREAD_COUNT - 1 do
+    begin
+    Threads[I]:=TSpinLockRelaxedThread.Create(True);
+    Threads[I].FreeOnTerminate:=False;
+    Threads[I].Start;
+    end;
+
+  for I:=0 to THREAD_COUNT - 1 do
+    begin
+    Threads[I].WaitFor;
+    Threads[I].Free;
+    end;
+
+  EndTime:=Now;
+  Result:=MilliSecondsBetween(EndTime, StartTime);
+  WriteLn('TSpinLock (Relaxed) Time: ', Result, ' ms');
+  WriteLn('Counter: ', Counter);
+  WriteLn;
+end;
+
+function RunCSBenchmark: Int64;
+var
+  Threads: array of TCriticalSectionThread;
+  I: Integer;
+  StartTime, EndTime: TDateTime;
+begin
+  WriteLn('--- Testing TCriticalSection Performance ---');
+  CS:=TCriticalSection.Create;
+  Counter:=0;
+  SetLength(Threads, THREAD_COUNT);
+
+  WriteLn('Starting ', THREAD_COUNT, ' threads, ', INCREMENTS_PER_THREAD, ' ops each.');
+  StartTime:=Now;
+
+  for I:=0 to THREAD_COUNT - 1 do
+    begin
+    Threads[I]:=TCriticalSectionThread.Create(True);
+    Threads[I].FreeOnTerminate:=False;
+    Threads[I].Start;
+    end;
+
+  for I:=0 to THREAD_COUNT - 1 do
+    begin
+    Threads[I].WaitFor;
+    Threads[I].Free;
+    end;
+
+  EndTime:=Now;
+  CS.Free;
+  Result:=MilliSecondsBetween(EndTime, StartTime);
+  WriteLn('TCriticalSection Time: ', Result, ' ms');
+  WriteLn('Counter: ', Counter);
+  WriteLn;
+end;
+
+procedure TestRecursion;
+begin
+  WriteLn('--- Testing Recursion (Thread Tracking) ---');
+  MySpinLock:=TSpinLock.Create(True);
+  WriteLn('Entering first time...');
+  MySpinLock.Enter;
+  try
+    WriteLn('Entering second time (recursive)...');
+    MySpinLock.Enter;
+    try
+      WriteLn('Inside recursive lock.');
+    finally
+      MySpinLock.Exit;
+    end;
+    WriteLn('Exited once.');
+  finally
+    MySpinLock.Exit;
+  end;
+  WriteLn('Exited twice. Success.');
+  WriteLn;
+end;
+
+procedure TestTryEnter;
+begin
+  WriteLn('--- Testing TryEnter ---');
+  MySpinLock:=TSpinLock.Create(False);
+  if MySpinLock.TryEnter then
+    begin
+    WriteLn('Acquired lock with TryEnter.');
+    if not MySpinLock.TryEnter then
+      WriteLn('Correctly failed to acquire already held lock with TryEnter.')
+    else
+      WriteLn('FAILURE: Acquired already held lock without tracking!');
+    MySpinLock.Exit;
+    end
+  else
+    WriteLn('FAILURE: Could not acquire free lock with TryEnter.');
+  WriteLn;
+end;
+
+var
+  SpinTime, SpinRelaxedTime, CSTime: Int64;
+begin
+  try
+    TestRecursion;
+    TestTryEnter;
+    
+    SpinTime:=RunSpinLockBenchmark;
+    SpinRelaxedTime:=RunSpinLockRelaxedBenchmark;
+    CSTime:=RunCSBenchmark;
+
+    WriteLn('--- Results ---');
+    WriteLn('TSpinLock (Strict):  ', SpinTime, ' ms');
+    WriteLn('TSpinLock (Relaxed): ', SpinRelaxedTime, ' ms');
+    WriteLn('TCriticalSection:    ', CSTime, ' ms');
+    
+    if SpinRelaxedTime < SpinTime then
+      WriteLn('Relaxed SpinLock was faster by ', SpinTime - SpinRelaxedTime, ' ms')
+    else
+      WriteLn('Relaxed SpinLock was slower/equal (Difference: ', SpinTime - SpinRelaxedTime, ' ms)');
+
+  except
+    on E: Exception do
+      WriteLn('Test failed with exception: ', E.ClassName, ': ', E.Message);
+  end;
+end.

+ 177 - 0
packages/fcl-base/src/syncobjs.pp

@@ -261,6 +261,35 @@ type
     property Count: Integer read FCount;
     property NextSpinCycleWillYield: Boolean read GetNextSpinCycleWillYield;
   end;
+  
+  // Fast lock, to be used only when lock is held for short times: uses the spinner.
+Type
+  { TSpinLock }
+
+  TSpinLock = record
+  private
+    FLock: LongInt;
+    FOwningThread: TThreadID;
+    FRecursionCount: Integer;
+    FThreadTracking: Boolean;
+    function GetIsLocked: Boolean; inline;
+    function GetIsLockedByCurrentThread: Boolean;
+    function GetIsThreadTrackingEnabled: Boolean; inline;
+    function GetIsOwnedByCurrentThread: Boolean; inline;
+  public
+    constructor Create(EnableThreadTracking: Boolean);
+    procedure Enter; inline;
+    procedure Exit(PublishNow: Boolean = True); inline;
+    function TryEnter: Boolean; overload; inline;
+    function TryEnter(Timeout: Cardinal): Boolean; overload; inline;
+    function TryEnter(const Timeout: TTimeSpan): Boolean; overload; inline;
+
+    property IsLocked: Boolean read GetIsLocked;
+    property IsOwnedByCurrentThread: Boolean read GetIsOwnedByCurrentThread;
+    property IsLockedByCurrentThread: Boolean read GetIsLockedByCurrentThread;
+    property IsThreadTrackingEnabled: Boolean read GetIsThreadTrackingEnabled;
+  end;
+
 
   // Guardian pattern. Use to see if an object was freed or not by adding a guardian instance to it.
 
@@ -1222,6 +1251,154 @@ begin
   until (lElapsedTime >= lWaitTime) or lWait.NextSpinCycleWillYield;
 end;
 
+{ ---------------------------------------------------------------------
+  TSpinLock
+  ---------------------------------------------------------------------}
+
+
+function TSpinLock.GetIsLocked: Boolean;
+
+begin
+  Result:=FLock <> 0;
+end;
+
+
+function TSpinLock.GetIsThreadTrackingEnabled: Boolean; 
+
+begin
+  Result:=FThreadTracking;
+end;
+
+
+function TSpinLock.GetIsOwnedByCurrentThread: Boolean;
+
+begin
+  Result:=(FOwningThread = GetCurrentThreadId);
+end;
+
+
+function TSpinLock.GetIsLockedByCurrentThread: Boolean;
+
+begin
+  Result:=GetIsThreadTrackingEnabled and GetIsLocked and GetIsOwnedByCurrentThread
+end;
+
+
+constructor TSpinLock.Create(EnableThreadTracking: Boolean);
+ 
+begin
+  FLock:=0;
+  FOwningThread:=0;
+  FRecursionCount:=0;
+  FThreadTracking:=EnableThreadTracking;
+end;
+
+
+procedure TSpinLock.Enter;
+
+var
+  Spinner: TSpinWait;
+  CT: TThreadID;
+  
+begin
+  if FThreadTracking then
+    begin
+    CT:=GetCurrentThreadId;
+    if (FOwningThread=CT) then
+      begin
+      Inc(FRecursionCount);
+      System.Exit;
+      end;
+    end;
+
+  Spinner.Reset;
+  while TInterlocked.CompareExchange(FLock,1,0)<>0 do
+    Spinner.SpinCycle;
+
+  if FThreadTracking then
+    begin
+    FOwningThread:=GetCurrentThreadId;
+    FRecursionCount:=1;
+    end;
+end;
+
+
+procedure TSpinLock.Exit(PublishNow: Boolean);
+
+var
+  CT: TThreadID;
+
+begin
+  if FThreadTracking then
+    begin
+    CT:=GetCurrentThreadId;
+    if (FOwningThread<>CT) then
+      raise ELockException.Create('Thread does not own the lock');
+    
+    Dec(FRecursionCount);
+    if (FRecursionCount>0) then 
+      System.Exit;
+    FOwningThread:=TThreadID(0);
+    end;
+  
+  if PublishNow then
+    TInterlocked.Exchange(FLock, 0)
+  else
+    begin
+    // Not 100% sure about this one ?
+    ReadWriteBarrier;
+    FLock:=0;
+    end;
+end;
+
+
+function TSpinLock.TryEnter: Boolean;
+
+begin
+  if FThreadTracking and IsOwnedByCurrentThread then
+    begin
+    Inc(FRecursionCount);
+    System.Exit(True);
+    end;
+
+  Result:=TInterlocked.CompareExchange(FLock,1,0)=0;
+  
+  if Result and FThreadTracking then
+  begin
+    FOwningThread:=GetCurrentThreadId;
+    FRecursionCount:=1;
+  end;
+end;
+
+function TSpinLock.TryEnter(const Timeout: TTimeSpan): Boolean;
+var
+  LSpinner: TSpinWait;
+  LStart: QWord;
+  LTotalMs: QWord;
+begin
+  if TryEnter then 
+    System.Exit(True);
+
+  LTotalMs:=Round(Timeout.TotalMilliseconds);
+  LStart:=GetTickCount64;
+  LSpinner.Reset;
+
+  while (GetTickCount64-LStart)<LTotalMs do
+    begin
+    LSpinner.SpinCycle;
+    if TryEnter then 
+      System.Exit(True);
+    end;
+  Result:=False;
+end;
+
+
+function TSpinLock.TryEnter(Timeout: Cardinal): Boolean;
+
+begin
+  Result:=TryEnter(TTimeSpan.FromMilliseconds(Timeout));
+end;
+
 { ---------------------------------------------------------------------
   TGuardian
   ---------------------------------------------------------------------}

+ 159 - 0
packages/libtar/tests/tlibtar1.pp

@@ -0,0 +1,159 @@
+{ Simple libtar test - creates a tar archive with a few entries }
+program libtar_simple_test;
+
+{$mode objfpc}{$H+}
+
+uses
+  SysUtils, libtar;
+
+const
+  TAR_FILENAME = 'simple_test.tar';
+  CONTENT_1 = 'Hello, World!';
+  CONTENT_2 = 'Some data in a subdirectory';
+  CONTENT_3 = 'hello.txt';
+  CONTENT_4 = 'hello.txt';
+
+  TotalEntries = 4;
+
+var
+  TW: TTarWriter;
+  TA: TTarArchive;
+  DirRec: TTarDirRec;
+  Content: RawByteString;
+  EntryCount: Integer;
+  Errors: Integer;
+begin
+  Errors := 0;
+
+  WriteLn('Creating tar archive: ', TAR_FILENAME);
+
+  try
+    TW := TTarWriter.Create(TAR_FILENAME);
+    try
+        { Set default permissions }
+        TW.Permissions := [tpReadByOwner, tpWriteByOwner, tpReadByGroup, tpReadByOther];
+
+        { Add a simple text file }
+        WriteLn('  Adding: hello.txt');
+        TW.AddString(CONTENT_1, 'hello.txt', Now);
+
+        { Add a file in a subdirectory }
+        WriteLn('  Adding: subdir/data.txt');
+        TW.AddString(CONTENT_2, 'subdir/data.txt', Now);
+
+        { Add a directory entry }
+        WriteLn('  Adding: emptydir/');
+        TW.AddDir(CONTENT_4, Now);
+
+        { Add a symbolic link }
+        WriteLn('  Adding: link.txt -> hello.txt');
+        TW.AddSymbolicLink('link.txt', CONTENT_3, Now);
+
+        TW.Finalize;
+    finally
+        TW.Free;
+    end;
+
+    WriteLn('Done. Archive created successfully.');
+    WriteLn;
+
+    WriteLn('=== Reading and verifying archive ===');
+    WriteLn;
+
+    TA := TTarArchive.Create(TAR_FILENAME);
+    try
+        EntryCount := 0;
+
+        while TA.FindNext(DirRec) do
+        begin
+        Inc(EntryCount);
+        WriteLn('Entry ', EntryCount, ':');
+        WriteLn('  Name: ', DirRec.Name);
+        WriteLn('  Size: ', DirRec.Size);
+        WriteLn('  Type: ', FILETYPE_NAME[DirRec.FileType]);
+
+        if DirRec.FileType = ftSymbolicLink then
+            begin
+            if DirRec.LinkName <> CONTENT_3 then
+                begin
+                Inc(Errors);
+                writeln('  Wrong link value');
+                end;
+            WriteLn('  Link: -> ', DirRec.LinkName);
+            end;
+
+        if DirRec.FileType = ftDirectory then
+            begin
+            if DirRec.Name <> CONTENT_4 then
+                begin
+                Inc(Errors);
+                writeln('  Wrong directory Name');
+                end;
+            end;
+
+        if DirRec.ChecksumOK then
+            WriteLn('  Checksum: OK')
+        else
+        begin
+            WriteLn('  Checksum: FAILED');
+            Inc(Errors);
+        end;
+
+        { Verify content for regular files }
+        if DirRec.FileType = ftNormal then
+        begin
+            Content := TA.ReadFile;
+
+            if DirRec.Name = 'hello.txt' then
+            begin
+            if Content = CONTENT_1 then
+                WriteLn('  Content: verified OK')
+            else
+            begin
+                WriteLn('  Content: MISMATCH');
+                Inc(Errors);
+            end;
+            end
+            else if DirRec.Name = 'subdir/data.txt' then
+            begin
+            if Content = CONTENT_2 then
+                WriteLn('  Content: verified OK')
+            else
+            begin
+                WriteLn('  Content: MISMATCH');
+                Inc(Errors);
+            end;
+            end;
+        end;
+
+        WriteLn;
+        end;
+    finally
+        TA.Free;
+    end;
+
+    { === PART 3: Summary === }
+    WriteLn('=== Summary ===');
+    WriteLn;
+    WriteLn('Total entries: ', EntryCount);
+
+    if EntryCount <> TotalEntries then
+    begin
+        WriteLn('ERROR: Expected ',TotalEntries,' entries!');
+        Inc(Errors);
+    end;
+
+    if Errors = 0 then
+        WriteLn('All tests PASSED.')
+    else
+        WriteLn('FAILED with ', Errors, ' error(s).');
+
+    WriteLn;
+
+  finally
+    { Cleanup }
+    DeleteFile(TAR_FILENAME);
+  end;
+
+  Halt(Errors);
+end.

Разница между файлами не показана из-за своего большого размера
+ 155 - 145
packages/vcl-compat/src/system.threading.pp


+ 8 - 2
tests/Makefile

@@ -2,7 +2,7 @@
 # Don't edit, this file is generated by FPCMake Version 2.0.0
 #
 default: allexectests
-MAKEFILETARGETS=i386-linux i386-go32v2 i386-win32 i386-os2 i386-freebsd i386-beos i386-haiku i386-netbsd i386-solaris i386-netware i386-openbsd i386-wdosx i386-darwin i386-emx i386-watcom i386-netwlibc i386-wince i386-embedded i386-symbian i386-nativent i386-iphonesim i386-android i386-aros m68k-linux m68k-netbsd m68k-amiga m68k-atari m68k-palmos m68k-macosclassic m68k-embedded m68k-sinclairql m68k-human68k powerpc-linux powerpc-netbsd powerpc-amiga powerpc-macosclassic powerpc-darwin powerpc-morphos powerpc-embedded powerpc-wii powerpc-aix sparc-linux sparc-netbsd sparc-solaris sparc-embedded x86_64-linux x86_64-freebsd x86_64-haiku x86_64-netbsd x86_64-solaris x86_64-openbsd x86_64-darwin x86_64-win64 x86_64-embedded x86_64-iphonesim x86_64-android x86_64-aros x86_64-dragonfly arm-linux arm-netbsd arm-palmos arm-wince arm-gba arm-nds arm-embedded arm-symbian arm-android arm-aros arm-freertos arm-ios powerpc64-linux powerpc64-darwin powerpc64-embedded powerpc64-aix powerpc64-freebsd avr-embedded armeb-linux armeb-embedded mips-linux mipsel-linux mipsel-embedded mipsel-android mipsel-ps1 mips64-linux mips64el-linux jvm-java jvm-android i8086-embedded i8086-msdos i8086-win16 aarch64-linux aarch64-freebsd aarch64-darwin aarch64-win64 aarch64-embedded aarch64-iphonesim aarch64-android aarch64-ios wasm32-embedded wasm32-wasip1 wasm32-wasip1threads wasm32-wasip2 sparc64-linux riscv32-linux riscv32-embedded riscv32-freertos riscv64-linux riscv64-embedded xtensa-linux xtensa-embedded xtensa-freertos z80-embedded z80-zxspectrum z80-msxdos z80-amstradcpc loongarch64-linux
+MAKEFILETARGETS=i386-linux i386-go32v2 i386-win32 i386-os2 i386-freebsd i386-beos i386-haiku i386-netbsd i386-solaris i386-qnx i386-netware i386-openbsd i386-wdosx i386-darwin i386-emx i386-watcom i386-netwlibc i386-wince i386-embedded i386-symbian i386-nativent i386-iphonesim i386-android i386-aros m68k-linux m68k-netbsd m68k-amiga m68k-atari m68k-palmos m68k-macosclassic m68k-embedded m68k-sinclairql m68k-human68k powerpc-linux powerpc-netbsd powerpc-amiga powerpc-macosclassic powerpc-darwin powerpc-morphos powerpc-embedded powerpc-wii powerpc-aix sparc-linux sparc-netbsd sparc-solaris sparc-embedded x86_64-linux x86_64-freebsd x86_64-haiku x86_64-netbsd x86_64-solaris x86_64-openbsd x86_64-darwin x86_64-win64 x86_64-embedded x86_64-iphonesim x86_64-android x86_64-aros x86_64-dragonfly arm-linux arm-netbsd arm-palmos arm-wince arm-gba arm-nds arm-embedded arm-symbian arm-android arm-aros arm-freertos arm-ios powerpc64-linux powerpc64-darwin powerpc64-embedded powerpc64-aix powerpc64-freebsd avr-embedded armeb-linux armeb-embedded mips-linux mipsel-linux mipsel-embedded mipsel-android mipsel-ps1 mips64-linux mips64el-linux jvm-java jvm-android i8086-embedded i8086-msdos i8086-win16 aarch64-linux aarch64-freebsd aarch64-darwin aarch64-win64 aarch64-embedded aarch64-iphonesim aarch64-android aarch64-ios wasm32-embedded wasm32-wasip1 wasm32-wasip1threads wasm32-wasip2 sparc64-linux riscv32-linux riscv32-embedded riscv32-freertos riscv64-linux riscv64-embedded xtensa-linux xtensa-embedded xtensa-freertos z80-embedded z80-zxspectrum z80-msxdos z80-amstradcpc loongarch64-linux
 BSDs = freebsd netbsd openbsd darwin dragonfly
 UNIXs = linux $(BSDs) solaris qnx haiku aix
 LIMIT83fs = go32v2 os2 emx watcom msdos win16 atari human68k
@@ -393,6 +393,9 @@ endif
 ifeq ($(CPU_OS_TARGET),i386-solaris)
 override TARGET_PROGRAMS+=gparmake createlst
 endif
+ifeq ($(CPU_OS_TARGET),i386-qnx)
+override TARGET_PROGRAMS+=gparmake createlst
+endif
 ifeq ($(CPU_OS_TARGET),i386-netware)
 override TARGET_PROGRAMS+=gparmake createlst
 endif
@@ -1543,6 +1546,9 @@ endif
 ifeq ($(CPU_OS_TARGET),i386-solaris)
 REQUIRE_PACKAGES_RTL=1
 endif
+ifeq ($(CPU_OS_TARGET),i386-qnx)
+REQUIRE_PACKAGES_RTL=1
+endif
 ifeq ($(CPU_OS_TARGET),i386-netware)
 REQUIRE_PACKAGES_RTL=1
 endif
@@ -2603,7 +2609,7 @@ TESTDIRECTDIRS=
 TESTSUBDIRS=cg cg/variants cg/cdecl cpu8/avr cpu16 cpu16/i8086 library opt wasm $(addprefix units/,$(TESTUNITDIRS))
 TESTPACKAGESDIRS=bzip2 cocoaint fcl-base fcl-db fcl-image fcl-registry fcl-xml hash rtl-objpas univint webtbs win-base zlib
 TESTPACKAGESUBDIRS=$(addprefix packages/,$(TESTPACKAGESDIRS))
-TESTPACKAGESDIRECTDIRS=rtl-objpas rtl-generics hash regexpr fcl-registry fcl-passrc fcl-json fcl-image pastojs fcl-process
+TESTPACKAGESDIRECTDIRS=rtl-objpas rtl-generics hash regexpr fcl-registry fcl-passrc fcl-json fcl-image pastojs fcl-process libtar
 TESTPACKAGESDIRECTSUBDIRS=$(addprefix ../packages/,$(addsuffix /tests,$(TESTPACKAGESDIRECTDIRS)))
 ifdef QUICKTEST
 export QUICKTEST

+ 1 - 1
tests/Makefile.fpc

@@ -175,7 +175,7 @@ TESTSUBDIRS=cg cg/variants cg/cdecl cpu8/avr cpu16 cpu16/i8086 library opt wasm
 TESTPACKAGESDIRS=bzip2 cocoaint fcl-base fcl-db fcl-image fcl-registry fcl-xml hash rtl-objpas univint webtbs win-base zlib
 
 TESTPACKAGESUBDIRS=$(addprefix packages/,$(TESTPACKAGESDIRS))
-TESTPACKAGESDIRECTDIRS=rtl-objpas rtl-generics hash regexpr fcl-registry fcl-passrc fcl-json fcl-image pastojs fcl-process
+TESTPACKAGESDIRECTDIRS=rtl-objpas rtl-generics hash regexpr fcl-registry fcl-passrc fcl-json fcl-image pastojs fcl-process libtar
 TESTPACKAGESDIRECTSUBDIRS=$(addprefix ../packages/,$(addsuffix /tests,$(TESTPACKAGESDIRECTDIRS)))
 
 ifdef QUICKTEST

Некоторые файлы не были показаны из-за большого количества измененных файлов