/******************************************************************************/ /* */ /* TempTestCase.cls - ooTestCase subclass for tests with temporary files */ /* ===================================================================== */ /* */ /* This program is part of the Rexx Parser package */ /* [See https://rexx.epbcn.com/rexx-parser/] */ /* */ /* Copyright (c) 2024-2026 Josep Maria Blasco */ /* */ /* License: Apache License 2.0 (https://www.apache.org/licenses/LICENSE-2.0) */ /* */ /* Version history: */ /* */ /* Date Version Details */ /* -------- ------- --------------------------------------------------------- */ /* 20261014 0.7 First version */ /* */ /* Temporary files and folders are created with the framework's */ /* .TemporaryTestFile and .TemporaryTestDirectory classes in the platform's */ /* temporary directory (.File~temporaryPath: TMPDIR or /tmp on Unix-like */ /* systems, %TEMP% on Windows), as the ooRexx test suite does, and are */ /* deleted afterwards. */ /* */ /* A test asks for them with */ /* */ /* path = self~tempFile(name [, contents]) -- a file, written */ /* path = self~tempDir(name) -- a folder, made */ /* */ /* and gets back the full path, as a string. Every name gets a suffix of its */ /* own (the process, the time and a counter), before its extension: */ /* tempFile("deck.md") is something like */ /* /tmp/rexx-parser-deck-1234-20261014181301770189-1.md. Two tests never */ /* share a name: on Windows, the uninit of an outgoing .TemporaryTestFile */ /* may run after the create of an incoming one of the same name, and delete */ /* the new file (see LoadProgramSource.testGroup). */ /* */ /* tearDown deletes everything the test asked for, whether the test passed */ /* or not: a test that fails half-way does not leave its files behind. A */ /* folder is deleted with all that is in it, including whatever the code */ /* under test wrote there (a backup "~", a .md2draw, a cache...). That is */ /* why a test that needs the code under test to write next to a file puts */ /* that file in a folder of its own. */ /* */ /* A subclass that has its own tearDown calls "self~tearDown:super". */ /* */ /* The deletion does not wait for the garbage collector to run uninit (a */ /* deletion that is left to uninit is not always done: see Assets and */ /* LoadProgramSource), and it never goes outside the temporary directory: */ /* rmTree refuses any path that is not in it. */ /* */ /******************************************************************************/ ::Requires "ooTest.frm" ::Class TempTestCase Subclass ooTestCase Public /******************************************************************************/ /* tempFile: a temporary file, with the given contents (a string, written as */ /* it is, or an array of lines). Returns its full path. */ /******************************************************************************/ ::Method tempFile Use Strict Arg name, contents = "" file = .TemporaryTestFile~new(.Nil, self~uniqueName(name)) file~create(contents) self~keepTemporary(file) Return file~fullName /******************************************************************************/ /* tempDir: a temporary folder, made. Returns its full path. */ /******************************************************************************/ ::Method tempDir Use Strict Arg name dir = .TemporaryTestDirectory~new(.Nil, self~uniqueName(name)) dir~create self~keepTemporary(dir) Return dir~fullName /******************************************************************************/ /* tearDown: delete what this test asked for, the last one first. */ /******************************************************************************/ ::Method tearDown Expose temporaries If \ Var("temporaries") Then Return Loop i = temporaries~items To 1 By -1 self~rmTree(temporaries[i]~fullName) End temporaries = .Array~new /******************************************************************************/ /* rmTree: delete a file, or a folder with all that is in it. Only inside the */ /* temporary directory. */ /* */ /* Depth-first, with no path patterns and no assumption about the order a */ /* listing comes back in (the lessons of Assets.testGroup, v180-v183). A link */ /* is deleted, never followed. */ /******************************************************************************/ ::Method rmTree Use Strict Arg path f = .File~new(path) If \ self~isTemporary(f) Then Raise Syntax 93.900 Array ("rmTree: '"path"' is not in the temporary directory") If \ f~exists, \ SysIsFileLink(f~absolutePath) Then Return If f~isDirectory, \ SysIsFileLink(f~absolutePath) Then Do children = f~listFiles If children \== .Nil Then Do child Over children self~rmTree(child~absolutePath) End End f~setWritable discard = f~delete /* -- Helpers --------------------------------------------------------------- */ ::Method keepTemporary Private Expose temporaries Use Strict Arg file If \ Var("temporaries") Then temporaries = .Array~new temporaries~append(file) ::Method uniqueName Private Expose counter Use Strict Arg name If \ Var("counter") Then counter = 0 counter += 1 stamp = SysQueryProcess("PID")"-"Date("S")Time("L")~changeStr(":", "")~changeStr(".", "")"-"counter dot = name~lastPos(".") If dot > 1 Then Return "rexx-parser-"name~left(dot - 1)"-"stamp || name~substr(dot) Return "rexx-parser-"name"-"stamp ::Method isTemporary Private Use Strict Arg f sep = .File~separator temp = .File~temporaryPath~absolutePath If temp~right(1) \== sep Then temp ||= sep path = f~absolutePath If .File~isCaseSensitive Then Return path~startsWith(temp) & path~length > temp~length Return path~caselessStartsWith(temp) & path~length > temp~length