Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/vortex-test-common.yml
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,15 @@ jobs:
env:
TEST_VORTEX_DEBUG: ${{ vars.TEST_VORTEX_DEBUG }}

- name: Upload test artifacts
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
if: always()
with:
name: ${{github.job}}-${{ matrix.batch }}-test-artifacts
path: .vortex/tests/.logs
include-hidden-files: true
if-no-files-found: ignore

- name: Upload coverage report as an artifact
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
Expand Down
26 changes: 26 additions & 0 deletions .vortex/tests/phpunit/Functional/FunctionalTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ protected function tearDown(): void {

$test_failed = $this->status() instanceof Failure || $this->status() instanceof Error;

// Collect SUT test artifacts before cleanup.
$this->collectSutArtifacts($test_failed);

if ($test_failed) {
$this->logNote('Skipping cleanup as test has failed.');
$this->log(static::locationsInfo());
Expand All @@ -79,6 +82,29 @@ protected function tearDown(): void {
parent::tearDown();
}

protected function collectSutArtifacts(bool $test_failed): void {
// On failure, containers are still running but syncToHost() may not have
// been called before the failure. Try to sync artifacts from the container.
if ($test_failed && !$this->volumesMounted()) {
try {
$this->syncToHost('.logs');
}
catch (\Throwable) {
// Ignore - container may not be running or .logs may not exist.
}
}

$sut_logs = static::$sut . '/.logs';
if (!is_dir($sut_logs)) {
return;
}

$dest = dirname(__DIR__, 2) . '/.logs/sut/' . $this->name();
File::copy($sut_logs, $dest);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

$this->logNote('SUT test artifacts copied to: ' . $dest);
Comment on lines +102 to +109
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

# Find the File class being used
rg 'class File' --type php -A 5 | head -50

Repository: drevops/vortex

Length of output: 40


🏁 Script executed:

# Look for File class imports or usage
rg 'use.*File' .vortex/tests/phpunit/Functional/FunctionalTestCase.php

Repository: drevops/vortex

Length of output: 85


🏁 Script executed:

# Search for File::copy definition
rg 'function copy\(' --type php

Repository: drevops/vortex

Length of output: 40


🏁 Script executed:

# Check if File class is from a composer package
cat composer.json | jq '.require, .require-dev' 2>/dev/null || find . -name "composer.json" -type f -exec cat {} \; | head -100

Repository: drevops/vortex

Length of output: 4691


🌐 Web query:

alexskrypnyk/file 0.19.0 File::copy() documentation

💡 Result:

For alexskrypnyk/file v0.19.0 (released 2025-12-23), the README documents AlexSkrypnyk\File\File::copy() as a static utility to copy a file or a directory (the example shows copying a directory recursively). It’s intended to be used inside a try/catch for FileException. [1]

Example (from the v0.19.0 README):

use AlexSkrypnyk\File\Exception\FileException;
use AlexSkrypnyk\File\File;

try {
  File::copy('/path/to/source', '/path/to/destination');
} catch (FileException $e) {
  echo $e->getMessage();
}
``` [1]

**Where it’s listed:**
- “Available Methods” table: **`copy()` — Copy file or directory.** [1]

**Source:** Packagist README for `alexskrypnyk/file` **0.19.0**. [1]

References:  
[1] https://packagist.org/packages/alexskrypnyk/file

---

🌐 Web query:

`github AlexSkrypnyk file php copy method parent directories`

💡 Result:

I couldn’t find a specific `File.php` (or a `copy()` implementation) in an `AlexSkrypnyk` GitHub repo from that query, but in plain PHP the key point is: **`copy($from, $to)` will fail if the destination directory doesn’t exist**—it does not create parent directories for you. [1]

Create the parent dirs first (recursively), then copy:

```php
$from = $src;
$to   = $dst;

$dir = dirname($to);
if (!is_dir($dir)) {
  mkdir($dir, 0777, true); // recursive parent directories
}

if (!copy($from, $to)) {
  throw new RuntimeException("Failed to copy $from to $to");
}
  • mkdir(..., $recursive=true) creates nested/parent directories. [2]
  • copy() copies/overwrites the destination file, but won’t create missing directories. [1]

Sources: [1] [2]


Defensive directory creation + copy error handling.

The .logs/sut directory must be created explicitly before copying, and File::copy() throws FileException on failure. In tearDown(), artifact logging failures should not mask test results. Ensure the destination directory exists and handle copy exceptions gracefully.

♻️ Suggested hardening
-    $dest = dirname(__DIR__, 2) . '/.logs/sut/' . $test_id;
-    File::copy($sut_logs, $dest);
+    $dest_root = dirname(__DIR__, 2) . '/.logs/sut';
+    if (!is_dir($dest_root) && !mkdir($dest_root, 0777, TRUE) && !is_dir($dest_root)) {
+      $this->logNote('Unable to create SUT artifacts directory: ' . $dest_root);
+      return;
+    }
+    $dest = $dest_root . '/' . $test_id;
+    try {
+      File::copy($sut_logs, $dest);
+    }
+    catch (\Throwable $e) {
+      $this->logNote('Failed to copy SUT artifacts: ' . $e->getMessage());
+      return;
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$class_name = (new \ReflectionClass($this))->getShortName();
$test_id = $class_name . '_' . $this->name() . '_' . uniqid();
$test_id = (string) preg_replace('/[^a-zA-Z0-9_-]/', '_', $test_id);
$dest = dirname(__DIR__, 2) . '/.logs/sut/' . $test_id;
File::copy($sut_logs, $dest);
$this->logNote('SUT test artifacts copied to: ' . $dest);
$class_name = (new \ReflectionClass($this))->getShortName();
$test_id = $class_name . '_' . $this->name() . '_' . uniqid();
$test_id = (string) preg_replace('/[^a-zA-Z0-9_-]/', '_', $test_id);
$dest_root = dirname(__DIR__, 2) . '/.logs/sut';
if (!is_dir($dest_root) && !mkdir($dest_root, 0777, TRUE) && !is_dir($dest_root)) {
$this->logNote('Unable to create SUT artifacts directory: ' . $dest_root);
return;
}
$dest = $dest_root . '/' . $test_id;
try {
File::copy($sut_logs, $dest);
}
catch (\Throwable $e) {
$this->logNote('Failed to copy SUT artifacts: ' . $e->getMessage());
return;
}
$this->logNote('SUT test artifacts copied to: ' . $dest);
🤖 Prompt for AI Agents
In @.vortex/tests/phpunit/Functional/FunctionalTestCase.php around lines 102 -
109, In tearDown() where $dest is built and File::copy($sut_logs, $dest) is
called, ensure the destination directory exists (create dirname($dest) with
appropriate permissions) before copying, and wrap the File::copy call in a
try/catch that catches the FileException (or \Exception) so copy failures are
logged via $this->logNote or a warning but not rethrown (so they don't mask test
results); reference the $dest variable, File::copy, the surrounding tearDown()
method and $this->logNote when making this change.

}

/**
* {@inheritdoc}
*/
Expand Down
Loading