From 28770fe20d34f2ca0e81e1483735603ffdbe261f Mon Sep 17 00:00:00 2001 From: Viktor Scharf Date: Fri, 15 Aug 2025 13:44:35 +0200 Subject: [PATCH] run all tests with POSIX_WATCH_FS=true (#1342) --- .woodpecker.star | 2 +- tests/acceptance/bootstrap/CliContext.php | 162 +++++++++++++++++- .../collaborativePosixFS.feature | 69 ++++++-- 3 files changed, 211 insertions(+), 22 deletions(-) diff --git a/.woodpecker.star b/.woodpecker.star index d311d7bf4..69276760e 100644 --- a/.woodpecker.star +++ b/.woodpecker.star @@ -2089,7 +2089,7 @@ def opencloudServer(storage = "decomposed", accounts_hash_difficulty = 4, depend }, "commands": [ "apt-get update", - "apt-get install -y inotify-tools", + "apt-get install -y inotify-tools xattr", "%s init --insecure true" % dirs["opencloudBin"], "cat $OC_CONFIG_DIR/opencloud.yaml", "cp tests/config/woodpecker/app-registry.yaml $OC_CONFIG_DIR/app-registry.yaml", diff --git a/tests/acceptance/bootstrap/CliContext.php b/tests/acceptance/bootstrap/CliContext.php index 9c343baf3..a8b826ee6 100644 --- a/tests/acceptance/bootstrap/CliContext.php +++ b/tests/acceptance/bootstrap/CliContext.php @@ -41,7 +41,12 @@ class CliContext implements Context { */ public static function getUsersStoragePath(): string { $path = getenv('OC_STORAGE_PATH') ?: '/var/lib/opencloud/storage/users'; - return $path . '/users'; + // need for CI + $home = getenv('HOME'); + $path = preg_replace('#^~/#', $home . '/', $path); + $path = str_replace('$HOME', $home, $path); + + return rtrim($path, '/') . '/users'; } /** @@ -69,6 +74,51 @@ class CliContext implements Context { $this->spacesContext = BehatHelper::getContext($scope, $environment, 'SpacesContext'); } + /** + * expects a file to exist at the given path + * + * @param string $path + * @param string|null $sizeGb + * @param int $maxSeconds + * + * @return void + */ + private function waitForPath(string $path, ?string $sizeGb = null, int $maxSeconds = 10): void { + $escapedPath = escapeshellarg($path); + + for ($i = 0; $i < $maxSeconds * 5; $i++) { + if ($sizeGb) { + if (!preg_match('/^(\d+)gb$/i', $sizeGb, $matches)) { + throw new \InvalidArgumentException("Invalid size format: $sizeGb. Use formats like 1gb, 5gb."); + } + $targetBytes = (int)$matches[1] * 1024 * 1024 * 1024; + $body = [ + "command" => "[ -f $escapedPath ] && stat -c%s $escapedPath || echo 0", + "raw" => true + ]; + $data = json_decode((string)CliHelper::runCommand($body)->getBody(), true); + + if (isset($data['message']) && (int)trim($data['message']) >= $targetBytes) { + return; + } + } else { + $body = [ + "command" => "ls $escapedPath >/dev/null 2>&1 && echo exists || echo not_exists", + "raw" => true + ]; + $response = CliHelper::runCommand($body); + $data = json_decode((string)$response->getBody(), true); + + if (isset($data['message']) && trim($data['message']) === 'exists') { + return; + } + } + usleep(200000); + } + + throw new \Exception("Timeout waiting for: $path"); + } + /** * @Given the administrator has stopped the server * @@ -468,11 +518,14 @@ class CliContext implements Context { public function theAdministratorCreatesFolder(string $folder, string $user): void { $userUuid = $this->featureContext->getUserIdByUserName($user); $storagePath = $this->getUsersStoragePath(); + $fullPath = "$storagePath/$userUuid/$folder"; + $body = [ - "command" => "mkdir -p $storagePath/$userUuid/$folder", + "command" => "mkdir -p $fullPath", "raw" => true ]; $this->featureContext->setResponse(CliHelper::runCommand($body)); + $this->waitForPath($fullPath); sleep(1); } @@ -505,12 +558,96 @@ class CliContext implements Context { public function theAdministratorCreatesFile(string $file, string $content, string $user): void { $userUuid = $this->featureContext->getUserIdByUserName($user); $storagePath = $this->getUsersStoragePath(); + $fullPath = "$storagePath/$userUuid/$file"; $safeContent = escapeshellarg($content); $body = [ - "command" => "echo -n $safeContent > $storagePath/$userUuid/$file", + "command" => "echo -n $safeContent > $fullPath", "raw" => true ]; $this->featureContext->setResponse(CliHelper::runCommand($body)); + $this->waitForPath($fullPath); + sleep(1); + } + + /** + * @When the administrator creates the file :file with size :size for user :user on the POSIX filesystem + * + * @param string $file + * @param string $size Example: "1gb", "5gb" + * @param string $user + * + * @return void + */ + public function theAdministratorCreatesLargeFileWithSize(string $file, string $size, string $user): void { + $userUuid = $this->featureContext->getUserIdByUserName($user); + $storagePath = $this->getUsersStoragePath(); + + $size = strtolower($size); + if (!preg_match('/^(\d+)gb$/', $size, $matches)) { + throw new \InvalidArgumentException("Invalid size format: $size. Use formats like 1gb, 5gb."); + } + + $count = (int)$matches[1] * 1024; // 1GB = 1024M + $bs = '1M'; + $filePath = "$storagePath/$userUuid/$file"; + + $body = [ + "command" => "dd if=/dev/zero of=$filePath bs=$bs count=$count", + "raw" => true + ]; + $this->featureContext->setResponse(CliHelper::runCommand($body)); + $this->waitForPath($filePath, $size, 15); + sleep(7); + } + + /** + * @When the administrator creates :count files sequentially in the directory :dir for user :user on the POSIX filesystem + * + * @param int $count + * @param string $dir + * @param string $user + * + * @return void + */ + public function theAdministratorCreatesFilesSequentially(int $count, string $dir, string $user): void { + $userUuid = $this->featureContext->getUserIdByUserName($user); + $storagePath = $this->getUsersStoragePath() . "/$userUuid/$dir"; + $cmd = ''; + for ($i = 1; $i <= $count; $i++) { + $cmd .= "echo -n \"file $i content\" > $storagePath/file_$i.txt; "; + } + $body = [ + "command" => $cmd, + "raw" => true + ]; + $this->featureContext->setResponse(CliHelper::runCommand($body)); + $this->waitForPath("$storagePath/file_$count.txt"); + sleep(3); + } + + /** + * @When the administrator creates :count files in parallel in the directory :dir for user :user on the POSIX filesystem + * + * @param int $count + * @param string $dir + * @param string $user + * + * @return void + */ + public function theAdministratorCreatesFilesInParallel(int $count, string $dir, string $user): void { + $userUuid = $this->featureContext->getUserIdByUserName($user); + $storagePath = $this->getUsersStoragePath() . "/$userUuid/$dir"; + $cmd = "mkdir -p $storagePath; "; + for ($i = 1; $i <= $count; $i++) { + $cmd .= "echo -n \"parallel file $i content\" > $storagePath/parallel_$i.txt & "; + } + $cmd .= "wait"; + $body = [ + "command" => $cmd, + "raw" => true + ]; + $this->featureContext->setResponse(CliHelper::runCommand($body)); + $this->waitForPath("$storagePath/parallel_$count.txt"); sleep(1); } @@ -712,4 +849,23 @@ class CliContext implements Context { $this->featureContext->setResponse(CliHelper::runCommand($body)); sleep(1); } + + /** + * @When the administrator checks the attribute :attribute of file :file for user :user on the POSIX filesystem + * + * @param string $attribute + * @param string $file + * @param string $user + * + * @return void + */ + public function theAdminChecksTheAttributeOfFileForUser(string $attribute, string $file, string $user): void { + $userUuid = $this->featureContext->getUserIdByUserName($user); + $storagePath = $this->getUsersStoragePath(); + $body = [ + "command" => "xattr -p -slz " . escapeshellarg($attribute) . " $storagePath/$userUuid/$file", + "raw" => true + ]; + $this->featureContext->setResponse(CliHelper::runCommand($body)); + } } diff --git a/tests/acceptance/features/collaborativePosix/collaborativePosixFS.feature b/tests/acceptance/features/collaborativePosix/collaborativePosixFS.feature index 73c1ae2ca..4ec596b55 100644 --- a/tests/acceptance/features/collaborativePosix/collaborativePosixFS.feature +++ b/tests/acceptance/features/collaborativePosix/collaborativePosixFS.feature @@ -4,26 +4,54 @@ Feature: create a resources using collaborative posixfs Background: Given the config "STORAGE_USERS_POSIX_WATCH_FS" has been set to "true" And user "Alice" has been created with default attributes + And user "Alice" has created folder "/firstFolder" + + + Scenario: administrator lists the content of the POSIX storage + Given user "Alice" has uploaded file with content "content" to "test.txt" + When the administrator lists the content of the POSIX storage folder of user "Alice" + Then the command output should contain "firstFolder" + And the command output should contain "test.txt" Scenario: create folder - Given user "Alice" has uploaded file with content "content" to "textfile.txt" When the administrator creates the folder "myFolder" for user "Alice" on the POSIX filesystem Then the command should be successful - When the administrator lists the content of the POSIX storage folder of user "Alice" - Then the command output should contain "myFolder" And as "Alice" folder "/myFolder" should exist + Scenario: create nested folder + When the administrator creates the folder "deep/nested/structure/myFolder" for user "Alice" on the POSIX filesystem + Then the command should be successful + And as "Alice" folder "/deep/nested/structure/myFolder" should exist + + Scenario: create file - Given user "Alice" has created folder "/folder" When the administrator creates the file "test.txt" with content "content" for user "Alice" on the POSIX filesystem Then the command should be successful - When the administrator lists the content of the POSIX storage folder of user "Alice" - Then the command output should contain "test.txt" And the content of file "/test.txt" for user "Alice" should be "content" + Scenario: create large file + When the administrator creates the file "largefile.txt" with size "1gb" for user "Alice" on the POSIX filesystem + Then the command should be successful + And as "Alice" file "/largefile.txt" should exist + + + Scenario: creates files sequentially in a folder + When the administrator creates 50 files sequentially in the directory "firstFolder" for user "Alice" on the POSIX filesystem + Then the command should be successful + And the content of file "/firstFolder/file_1.txt" for user "Alice" should be "file 1 content" + And the content of file "/firstFolder/file_50.txt" for user "Alice" should be "file 50 content" + + + Scenario: creates files in parallel in a folder + When the administrator creates 100 files in parallel in the directory "firstFolder" for user "Alice" on the POSIX filesystem + Then the command should be successful + And the content of file "/firstFolder/parallel_1.txt" for user "Alice" should be "parallel file 1 content" + And the content of file "/firstFolder/parallel_100.txt" for user "Alice" should be "parallel file 100 content" + + Scenario: edit file Given user "Alice" has uploaded file with content "content" to "test.txt" When the administrator puts the content "new" into the file "test.txt" in the POSIX storage folder of user "Alice" @@ -37,11 +65,10 @@ Feature: create a resources using collaborative posixfs Scenario: copy file to folder - Given user "Alice" has created folder "/folder" - And user "Alice" has uploaded file with content "content" to "test.txt" - When the administrator copies the file "test.txt" to the folder "folder" for user "Alice" on the POSIX filesystem + Given user "Alice" has uploaded file with content "content" to "test.txt" + When the administrator copies the file "test.txt" to the folder "firstFolder" for user "Alice" on the POSIX filesystem Then the command should be successful - And the content of file "/folder/test.txt" for user "Alice" should be "content" + And the content of file "/firstFolder/test.txt" for user "Alice" should be "content" Scenario: rename file @@ -52,11 +79,10 @@ Feature: create a resources using collaborative posixfs Scenario: move file to folder - Given user "Alice" has created folder "/folder" - And user "Alice" has uploaded file with content "content" to "test.txt" - When the administrator moves the file "test.txt" to the folder "folder" for user "Alice" on the POSIX filesystem + Given user "Alice" has uploaded file with content "content" to "test.txt" + When the administrator moves the file "test.txt" to the folder "firstFolder" for user "Alice" on the POSIX filesystem Then the command should be successful - And the content of file "/folder/test.txt" for user "Alice" should be "content" + And the content of file "/firstFolder/test.txt" for user "Alice" should be "content" And as "Alice" file "/test.txt" should not exist @@ -68,11 +94,10 @@ Feature: create a resources using collaborative posixfs Scenario: delete folder - Given user "Alice" has created folder "/folder" - And user "Alice" has uploaded file with content "content" to "/folder/test.txt" - When the administrator deletes the folder "folder" for user "Alice" on the POSIX filesystem + And user "Alice" has uploaded file with content "content" to "/firstFolder/test.txt" + When the administrator deletes the folder "firstFolder" for user "Alice" on the POSIX filesystem Then the command should be successful - And as "Alice" folder "folder" should not exist + And as "Alice" folder "firstFolder" should not exist Scenario: copy file from personal to project space @@ -155,3 +180,11 @@ Feature: create a resources using collaborative posixfs Then the command should be successful And for user "Brian" the content of the file "textfile.txt" of the space "Shares" should be "contentnew" And the public should be able to download file "textfile.txt" from the last link share with password "%public%" and the content should be "contentnew" + + @issue-1100 + Scenario: upload and rename file + When the administrator creates the file "test.txt" with content "content" for user "Alice" on the POSIX filesystem + And the administrator renames the file "test.txt" to "renamed.txt" for user "Alice" on the POSIX filesystem + And the administrator checks the attribute "user.oc.name" of file "renamed.txt" for user "Alice" on the POSIX filesystem + Then the command output should contain "renamed.txt" + And the content of file "/renamed.txt" for user "Alice" should be "content"