From 9e4d08ca31e5c641ab590e729e046188d13b2a08 Mon Sep 17 00:00:00 2001 From: Spacetown <40258682+spacetown@users.noreply.github.com> Date: Thu, 29 May 2025 22:53:48 +0200 Subject: [PATCH 1/3] Add deploy steps --- .github/workflows/CI.yml | 64 ++++++++++++++++++++++++++++++++-------- README.md | 2 ++ scripts/get_version.sh | 5 ++-- 3 files changed, 57 insertions(+), 14 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index dbbddcb..896615c 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -88,7 +88,7 @@ jobs: run: choco install ninja - name: Install gcovr if: ${{ matrix.BuildType == 'Profile' }} - run: pip install git+https://github.com/gcovr/gcovr.git # gcovr==8.3 + run: pip install gcovr==8.3 - name: Configure run: ./scripts/cmake.configure.sh -DCMAKE_BUILD_TYPE=${{ matrix.BuildType }} @@ -112,7 +112,6 @@ jobs: --filter src/ \ --exclude-noncode-lines build \ --txt coverage.txt \ - --markdown coverage.md --markdown-title "Test coverage report" --markdown-file-link 'https://github.com/Spacetown/FileOp/blob/${{ github.sha }}/{file}' \ --json coverage.json --json-pretty \ --html-single-page --html-title "GCOVR report for $(git rev-parse HEAD)" --html-details coverage.html cat coverage.txt @@ -131,25 +130,26 @@ jobs: name: app-windows${{ matrix.BuildType == 'Profile' && '-profile' || ''}} path: build/FileOp.7z - - name: Add job summary - if: ${{ matrix.BuildType == 'Profile' && always() }} - run: | - ( - cat coverage.md - echo "" - cat performance.txt - ) >> $GITHUB_STEP_SUMMARY - deploy: needs: - container-build - build runs-on: Windows-latest + permissions: + contents: write steps: - - name: Download artifacts + - uses: actions/checkout@v4 + - name: Set up environment + run: | + echo "FILEOP_VERSION=$(scripts/get_version.sh)" >> $GITHUB_ENV + - name: Download apps uses: actions/download-artifact@v4 with: pattern: app-* + - name: Download coverage report + uses: actions/download-artifact@v4 + with: + pattern: coverage # cspell:ignore oapp - name: Test container release build run: | @@ -163,3 +163,43 @@ jobs: run: | 7z x -oapp-windows-profile app-windows-profile/FileOp.7z ./app-windows-profile/FileOp.exe --help + + - name: Create release notes + run: | + sed -n '/^## / { p; :a; n; /^## /q; p; ba; }' Changelog.md | sed -e 's/^## /# /;' > release_notes.md + + - name: Add job summary + run: | + ( + cat release_notes.md + echo "" + echo "# Test coverage report" + echo "" + cat coverage.txt + echo "" + cat performance.txt + ) >> $GITHUB_STEP_SUMMARY + + - name: Create new tag + run: | + # Set git user info + git config --global user.email "noreply@zf.com" + git config --global user.name "FileOp authors" + + # Create the tag and print the output. + git tag -a "$FILEOP_VERSION" -m "$FILEOP_VERSION ($(date -I))" + git tag --list -n "$FILEOP_VERSION" + + - name: Push new tag + if: ${{ (github.repository == 'ZF-Group/FileOp') && (github.event.ref == 'refs/heads/main') && (env.FILEOP_VERSION != '0.0.0') }} + run: | + git push origin "refs/tags/$FILEOP_VERSION" + + - name: Create release and upload build artifacts + if: ${{ (github.repository == 'ZF-Group/FileOp') && (github.event.ref == 'refs/heads/main') && (env.FILEOP_VERSION != '0.0.0') }} + uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191 + with: + tag_name: ${{ env.FILEOP_VERSION }} + body_path: release_notes.md + files: ./app-windows/FileOp.exe + diff --git a/README.md b/README.md index e2a687c..9a13460 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ +[![Run FileOp CI](https://github.com/ZF-Group/FileOp/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/ZF-Group/FileOp/actions/workflows/CI.yml) + # FileOp Tool for general file operations under Windows with support of file names longer than MAX_PATH (260 characters). diff --git a/scripts/get_version.sh b/scripts/get_version.sh index 3a058cb..75c3eeb 100755 --- a/scripts/get_version.sh +++ b/scripts/get_version.sh @@ -4,9 +4,10 @@ set -e THIS_DIRECTORY="$(cd "$(dirname "${BASH_SOURCE[0]}" )" && pwd)" -CurrentTag="$(sed --silent -e '/^## / { s/^## //; p }' $THIS_DIRECTORY/../Changelog.md \ +CurrentTag="$(sed --silent -e '/^## / { s/^## //; p }' "$THIS_DIRECTORY/../Changelog.md" \ | head --lines 1 \ - | sed --silent -e '/^[0-9]*\.[0-9]*\.[0-9]*/ p')" + | sed --silent -e '/^[0-9]*\.[0-9]*\.[0-9]*/ p' \ + | tr -d '\r\n')" if [ -z "${CurrentTag}" ] ; then CurrentTag="0.0.0" fi From 6bdb2265e32566459ebffbfe19930acc37c9fce1 Mon Sep 17 00:00:00 2001 From: Spacetown <40258682+spacetown@users.noreply.github.com> Date: Thu, 29 May 2025 23:23:52 +0200 Subject: [PATCH 2/3] Add devcontainer --- .devcontainer/Dockerfile | 9 +++ .devcontainer/devcontainer.json | 24 ++++++++ .github/workflows/CI.yml | 37 ++++++++--- .gitignore | 1 - Changelog.md | 4 +- FileOp.code-workspace | 13 ++-- README.md | 19 +++--- cspell.json | 105 ++++++++++++++++---------------- scripts/cmake.configure.sh | 3 +- scripts/container.setup.sh | 9 +++ scripts/run_test_performance.sh | 86 +++++++++++--------------- src/CMakeLists.txt | 8 ++- src/FileOp.c | 2 +- src/Message.c | 6 +- src/OperationMkdir.cmake | 32 +++++----- src/mingw64.toolchain | 23 +++++++ 16 files changed, 230 insertions(+), 151 deletions(-) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json create mode 100755 scripts/container.setup.sh create mode 100644 src/mingw64.toolchain diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..83f0ba6 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,9 @@ +ARG DOCKER_OS=debian:bookworm + +FROM $DOCKER_OS + +COPY scripts/container.setup.sh /tmp/container.setup.sh + +RUN --mount=type=cache,target=/var/cache/apt <&1 | tee performance.txt + - name: Upload performance report + if: ${{ matrix.BuildType == 'Release' && always() }} + uses: actions/upload-artifact@v4 + with: + name: performance + path: performance.txt - name: Create coverage report if: ${{ matrix.BuildType == 'Profile' && always() }} @@ -123,13 +142,6 @@ jobs: name: coverage path: coverage.* - - name: Upload ZIP - if: always() - uses: actions/upload-artifact@v4 - with: - name: app-windows${{ matrix.BuildType == 'Profile' && '-profile' || ''}} - path: build/FileOp.7z - deploy: needs: - container-build @@ -149,7 +161,11 @@ jobs: - name: Download coverage report uses: actions/download-artifact@v4 with: - pattern: coverage + name: coverage + - name: Download performance report + uses: actions/download-artifact@v4 + with: + name: performance # cspell:ignore oapp - name: Test container release build run: | @@ -175,7 +191,9 @@ jobs: echo "" echo "# Test coverage report" echo "" + echo '```' cat coverage.txt + echo '```' echo "" cat performance.txt ) >> $GITHUB_STEP_SUMMARY @@ -187,7 +205,8 @@ jobs: git config --global user.name "FileOp authors" # Create the tag and print the output. - git tag -a "$FILEOP_VERSION" -m "$FILEOP_VERSION ($(date -I))" + sed -e "/^# / { s/^# //; s/$/ $(date -I)/; }" release_notes.md | tee commit_msg.txt + git tag -a "$FILEOP_VERSION" -F commit_msg.txt git tag --list -n "$FILEOP_VERSION" - name: Push new tag diff --git a/.gitignore b/.gitignore index 238ae03..bf82b9c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ /.venv/ /build/ -/dockcross diff --git a/Changelog.md b/Changelog.md index f0ae7fd..2fd3b79 100644 --- a/Changelog.md +++ b/Changelog.md @@ -3,10 +3,10 @@ -## Unreleased +## 1.9.0 - First release under BSD 3-Clause License. -- Use CMake and cross compiling with gcc-11. +- Use CMake and cross compiling with mingw-w64 to be able to build on all systems running Docker. ## 1.8.0 diff --git a/FileOp.code-workspace b/FileOp.code-workspace index 6fa6d05..6f6d1de 100644 --- a/FileOp.code-workspace +++ b/FileOp.code-workspace @@ -7,24 +7,19 @@ "settings": { "actionButtons": { "commands": [ - { - "name": "Setup env", - "command": "docker run --rm --platform linux/amd64 dockcross/windows-static-x64 > ./dockcross && chmod +x ./dockcross", - "singleInstance": true - }, { "name": "Configure (Profile)", - "command": "./dockcross --args '--platform linux/amd64' bash -c './scripts/cmake.configure.sh -DCMAKE_BUILD_TYPE=Profile'", + "command": "./scripts/cmake.configure.sh -DCMAKE_BUILD_TYPE=Profile", "singleInstance": true }, { "name": "Configure (Release)", - "command": "./dockcross --args '--platform linux/amd64' bash -c './scripts/cmake.configure.sh -DCMAKE_BUILD_TYPE=Release'", + "command": "./scripts/cmake.configure.sh -DCMAKE_BUILD_TYPE=Release", "singleInstance": true }, { "name": "Build", - "command": "./dockcross --args '--platform linux/amd64' bash -c './scripts/cmake.build.sh'", + "command": "./scripts/cmake.build.sh", "singleInstance": true } ], @@ -40,7 +35,7 @@ "seunlanlege.action-buttons", "streetsidesoftware.code-spell-checker", "EditorConfig.EditorConfig", - "ms-vscode.cpptools-extension-pack" + "ms-vscode.cpptools" ] // cspell:enable } diff --git a/README.md b/README.md index 9a13460..b91fa5c 100644 --- a/README.md +++ b/README.md @@ -11,19 +11,24 @@ Windows command line tools only support paths with a maximum length of 260 chara As workaround you need to subst the directory to a drive letter and delete the sub tree from there. -## Directory layout +## Usage -dir | description ---- | --- -`build` | *ignored*: Storage of build-results -`src` | Storage for source files -`tests` | Storage for test scripts +All supported commands are compiled into a single executable `FileOp.exe`. To get a list of commands +and the available options you can execute `FileOp.exe --help`. ## Development For development a workspace for `Visual Studio Code` is configured together with a cross compiler running under docker. +## Directory layout + +| dir | description | +| --------- | ----------------------------------- | +| `build` | *ignored*: Storage of build results | +| `scripts` | Storage for source files | +| `src` | Storage for source files | + ### Build The project uses CMake and ninja for building the executable. The CMake configuration step is executed by @@ -33,4 +38,4 @@ execute the tools. ### Test -To test the generated artifacts call `.\tools\test.cmd` or use `Terminal`->`Run Task...`->`Test` in the IDE. +The test are also written in CMake and executed by calling [scripts/cmake.test.sh](./scripts/cmake.test.sh). diff --git a/cspell.json b/cspell.json index 3ac0ba2..fd50026 100644 --- a/cspell.json +++ b/cspell.json @@ -1,52 +1,53 @@ -{ - "version": "0.2", - "files": [ - "/**" - ], - "ignorePaths": [ - "/.git/", - "src/mingw-unicode.c" - ], - "dictionaryDefinitions": [], - "dictionaries": [], - "enableGlobDot": true, - "words": [ - "choco", - "devcontainers", - "dockcross", - "endgroup", - "fileop", - "gcov", - "gcovr", - "mapfile", - "mklink", - "msys", - "noncode", - "popd", - "pushd", - "STREQUAL", - "venv", - "windres" - ], - "ignoreWords": [ - "WINXP", - "endforeach", - "endfunction", - "noninteractive", - "operationcopy", - "seunlanlege" - ], - "ignoreRegExpList": [ - // GH actions - "uses: [^\\s]+/[^\\s]+@[^\\s]+", - // Options - "--[a-z0-9-]+", - // CMAKE and GCC defines - "-DCMAKE[A-Z_]*", - "-DN?DEBUG", - // Functions - "tcs[a-z]+", - "[a-z]+printf" - ], - "import": [] -} \ No newline at end of file +{ + "version": "0.2", + "files": [ + "/**" + ], + "ignorePaths": [ + "/.git/", + "src/mingw-unicode.c" + ], + "dictionaryDefinitions": [], + "dictionaries": [], + "enableGlobDot": true, + "words": [ + "choco", + "cpptools", + "devcontainers", + "dockcross", + "endgroup", + "fileop", + "gcov", + "gcovr", + "mapfile", + "mklink", + "msys", + "noncode", + "popd", + "pushd", + "STREQUAL", + "venv", + "windres" + ], + "ignoreWords": [ + "WINXP", + "endforeach", + "endfunction", + "noninteractive", + "operationcopy", + "seunlanlege" + ], + "ignoreRegExpList": [ + // GH actions + "uses: [^\\s]+/[^\\s]+@[^\\s]+", + // Options + "--[a-z0-9-]+", + // CMAKE and GCC defines + "-DCMAKE[A-Z_]*", + "-DN?DEBUG", + // Functions + "tcs[a-z]+", + "[a-z]+printf" + ], + "import": [] +} diff --git a/scripts/cmake.configure.sh b/scripts/cmake.configure.sh index dafb163..bb5b20f 100755 --- a/scripts/cmake.configure.sh +++ b/scripts/cmake.configure.sh @@ -2,5 +2,4 @@ set -e THIS_DIRECTORY="$(cd "$(dirname "${BASH_SOURCE[0]}" )" && pwd)" -set -x -cmake --fresh -G Ninja "$@" -S $THIS_DIRECTORY/../src -B $THIS_DIRECTORY/../build +cmake --fresh -G Ninja "$@" -S $THIS_DIRECTORY/../src -B $THIS_DIRECTORY/../build --toolchain $THIS_DIRECTORY/../src/mingw64.toolchain diff --git a/scripts/container.setup.sh b/scripts/container.setup.sh new file mode 100755 index 0000000..df338e6 --- /dev/null +++ b/scripts/container.setup.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e + +THIS_DIRECTORY="$(cd "$(dirname "${BASH_SOURCE[0]}" )" && pwd)" +export DEBIAN_FRONTEND=noninteractive + +apt update +apt install -y mingw-w64 cmake ninja-build diff --git a/scripts/run_test_performance.sh b/scripts/run_test_performance.sh index 6257b5a..7564e8f 100755 --- a/scripts/run_test_performance.sh +++ b/scripts/run_test_performance.sh @@ -24,64 +24,50 @@ function append_rsp() { fi } +function time_it() { + echo -n " " + (time "$@") 2>&1 | sed --silent '/real/ { s/real\s*//; p; }' | tr '\r\n' ' ' + echo -n "|" +} + +append_rsp root 0 + echo """## Performance test -Creating lists for $max directories with with a depth of $max levels and $max files in each""" -time append_rsp root 0 -echo """Lists contain $(wc --lines < mkdir.rsp) directories and $(wc --lines < touch.rsp) files. -Longest filename has $(wc --max-line-length < touch.rsp) characters. """ sed --regexp-extended -s 's|/|\\\\|g' < mkdir.rsp > mkdir.backslash.rsp sed --regexp-extended -s 's|/|\\\\|g' < touch.rsp > touch.backslash.rsp ############################################################################### -echo """ -### Using cmd - -#### Creating directories""" -time xargs --max-chars=8000 --arg-file mkdir.backslash.rsp -I '{}' cmd.exe //c "mkdir {}" \; -echo " -#### Creating files" -time xargs --max-chars=8000 --arg-file touch.backslash.rsp -I '{}' cmd.exe //c "for %f in ( {} ) do @( echo. > %f )" \; -echo " -#### Removing tree" -time rm -rf root - +echo "| Environment | Create $(wc --lines < mkdir.rsp) directories | Create $(wc --lines < touch.rsp) files | Remove tree |" +echo "| ----------- | -------------------------------------------- | -------------------------------------- | ----------- |" ############################################################################### -echo """ -### Using bash - -#### Creating directories""" -time xargs --max-chars=8000 --arg-file mkdir.rsp mkdir -echo " -#### Creating files" -time xargs --max-chars=8000 --arg-file touch.rsp touch -echo " -#### Removing tree" -time rm -rf root - +echo -n "| cmd |" +time_it xargs --max-chars=8000 --arg-file mkdir.backslash.rsp -I '{}' cmd.exe //c "mkdir {}" \; +time_it xargs --max-chars=8000 --arg-file touch.backslash.rsp -I '{}' cmd.exe //c "for %f in ( {} ) do @( echo. > %f )" \; +time_it cmd.exe //c "rmdir /S /Q root" +echo "" ############################################################################### -echo """ -### Using FileOp - -#### Creating directories""" -time xargs --max-chars=8000 --arg-file mkdir.rsp FileOp.exe mkdir -echo " -#### Creating files" -time xargs --max-chars=8000 --arg-file touch.rsp FileOp.exe touch -echo " -#### Removing tree" -time FileOp.exe remove --recursive --force root - +echo -n "| bash |" +time_it xargs --max-chars=8000 --arg-file mkdir.rsp mkdir +time_it xargs --max-chars=8000 --arg-file touch.rsp touch +time_it rm -rf root +echo "" ############################################################################### -echo """ -### Using FileOp with rsp +echo -n "| FileOp |" +time_it xargs --max-chars=8000 --arg-file mkdir.rsp FileOp.exe mkdir +time_it xargs --max-chars=8000 --arg-file touch.rsp FileOp.exe touch +time_it FileOp.exe remove --recursive --force root +echo "" +############################################################################### +echo -n "| FileOp with rsp file |" +time_it FileOp.exe mkdir @mkdir.rsp +time_it FileOp.exe touch @touch.rsp +time_it FileOp.exe remove --recursive --force root +echo "" -#### Creating directories""" -time FileOp.exe mkdir @mkdir.rsp -echo " -#### Creating files" -time FileOp.exe touch @touch.rsp -echo " -#### Removing tree" -time FileOp.exe remove --recursive --force root +echo """ +If no response file is supported \`xargs\` is used for execution and needs to call the command +$((xargs --max-chars=8000 --arg-file mkdir.rsp echo) | wc --lines) times for the directories and +$((xargs --max-chars=8000 --arg-file touch.rsp echo) | wc --lines) times for the file creation. +""" \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 143dedb..bd74f8a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -119,7 +119,7 @@ project(FileOp C RC) ) test_fileop( NAME Run_${arg_COMMAND}_WithLonesomeDash - ARGS --debug ${arg_COMMAND} -- --xxx + ARGS -d ${arg_COMMAND} -- --xxx WORKING_DIRECTORY ${arg_WORKING_DIRECTORY} WILL_FAIL FAIL_REGULAR_EXPRESSION "-- detected, stop option parsing\\." @@ -263,6 +263,12 @@ project(FileOp C RC) PASS_REGULAR_EXPRESSION "FileOp\\.exe \\[\\] \\[ \\[\\] \\]" ) + test_fileop( + NAME Run_WithOptionH + ARGS -h + PASS_REGULAR_EXPRESSION "FileOp\\.exe \\[\\] \\[ \\[\\] \\]" + ) + test_fileop( NAME Run_WithUnknownCommand ARGS unknown diff --git a/src/FileOp.c b/src/FileOp.c index e2ef317..4381380 100644 --- a/src/FileOp.c +++ b/src/FileOp.c @@ -99,7 +99,7 @@ int _tmain(int argc, wchar_t *argv[]) { // GCOVR_EXCL_STOP DWORD ProgramNameLen = _tcslen(Ptr); - if (ProgramNameLen > PROGRAM_NAME_BUFFER_SIZE) { // GCOVR_EXCL_BR + if (ProgramNameLen > PROGRAM_NAME_BUFFER_SIZE) { // GCOVR_EXCL_BR_LINE // GCOVR_EXCL_START _tcscpy(ProgramName, _T("...")); _tcscat(ProgramName, &Ptr[ProgramNameLen - (PROGRAM_NAME_BUFFER_SIZE + 3 + 1)]); diff --git a/src/Message.c b/src/Message.c index 475b5a7..c303d0d 100644 --- a/src/Message.c +++ b/src/Message.c @@ -49,14 +49,14 @@ tResult printErr(LPCTSTR MsgTxt, ...) { */ tResult printLastError(LPCTSTR MsgTxt, ...) { DWORD dwLastError = GetLastError(); - if (dwLastError) { + if (dwLastError) { // GCOVR_EXCL_BR_LINE va_list vl; va_start(vl, MsgTxt); int MsgLength = _vsntprintf(MsgBuffer, SIZE_MSG_BUFFER, MsgTxt, vl); va_end(vl); MsgLength += _sntprintf(&MsgBuffer[MsgLength], SIZE_MSG_BUFFER - MsgLength, _T(": ")); - if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwLastError, MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), (LPTSTR)&MsgBuffer[MsgLength], - SIZE_MSG_BUFFER - MsgLength, NULL) == 0) { + if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwLastError, MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), // GCOVR_EXCL_BR_LINE + (LPTSTR)&MsgBuffer[MsgLength], SIZE_MSG_BUFFER - MsgLength, NULL) == 0) { _sntprintf(&MsgBuffer[MsgLength], SIZE_MSG_BUFFER - MsgLength, _T("GetLastError() = %d\n"), dwLastError); // GCOVR_EXCL_LINE } return printErr(_T("%s"), MsgBuffer); diff --git a/src/OperationMkdir.cmake b/src/OperationMkdir.cmake index 505c932..a98e2a8 100644 --- a/src/OperationMkdir.cmake +++ b/src/OperationMkdir.cmake @@ -5,43 +5,47 @@ test_fileop_command_common( test_fileop_check_filesystem( NAME Mkdir - ARGS mkdir mkdir - MUST_EXIST mkdir + ARGS mkdir dir + MUST_EXIST dir +) + +test_fileop_check_filesystem( + NAME MkdirTrailingSlash + ARGS mkdir dir/// + MUST_EXIST dir/// ) test_fileop_check_filesystem( NAME MkdirExistingDir - ARGS mkdir mkdir - DEPENDS Mkdir + PREPARE_COMMAND "mkdir dir" + ARGS mkdir dir WILL_FAIL - FAIL_REGULAR_EXPRESSION "FileOp\\.exe: error: Directory [A-Z]:\\\\.+\\\\mkdir already exists\\." + FAIL_REGULAR_EXPRESSION "FileOp\\.exe: error: Directory [A-Z]:\\\\.+\\\\dir already exists\\." ) test_fileop_check_filesystem( NAME MkdirExistingDirParents + PREPARE_COMMAND "mkdir dir" ARGS mkdir --parents mkdir - DEPENDS Mkdir ) test_fileop_check_filesystem( NAME MkdirDirStructure - ARGS mkdir mkdir/a/b/c - DEPENDS MkdirExistingDirParents + ARGS mkdir a/b/c WILL_FAIL - MUST_NOT_EXIST mkdir/a/b/c + MUST_NOT_EXIST a/b/c ) test_fileop_check_filesystem( NAME MkdirDirStructureParents - ARGS mkdir --parents mkdir/a/b/c - DEPENDS MkdirDirStructure - MUST_EXIST mkdir/a/b/c + ARGS mkdir -p a/b/c + MUST_EXIST a/b/c ) test_fileop_check_filesystem( NAME MkdirWithDashDash - ARGS mkdir -- --mkdir - MUST_EXIST --mkdir + ARGS mkdir -- --dir + MUST_EXIST --dir ) test_fileop_check_filesystem( diff --git a/src/mingw64.toolchain b/src/mingw64.toolchain new file mode 100644 index 0000000..08a5bfd --- /dev/null +++ b/src/mingw64.toolchain @@ -0,0 +1,23 @@ +# the name of the target operating system +set(CMAKE_SYSTEM_NAME Windows) + +# which compilers to use for C and C++ +set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc) +if(CMAKE_HOST_SYSTEM MATCHES Windows) + set(CMAKE_RC_COMPILER windres) +else () + set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres) +endif() + +# adjust the default behavior of the FIND_XXX() commands: +# search programs in the host environment +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + +# search headers and libraries in the target environment +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) + +set(CMAKE_C_FLAGS_PROFILE_INIT "--coverage") + +set(CMAKE_RC_FLAGS_INIT "--verbose") +set(CMAKE_EXE_LINKER_FLAGS_INIT "-static") From cee82e84cc1a3c5204ed8ca00d6d7244db0db905 Mon Sep 17 00:00:00 2001 From: Spacetown <40258682+spacetown@users.noreply.github.com> Date: Fri, 30 May 2025 16:40:51 +0200 Subject: [PATCH 3/3] Improve branch coverage --- src/BasicFileOp.c | 39 ++++++++++++++++++++------------------- src/OperationCopy.c | 2 +- src/OperationCopy.cmake | 8 ++++---- src/OperationMove.c | 8 ++++---- src/OperationMove.cmake | 17 +++++++++++++---- src/OperationRemove.c | 4 ++-- src/OperationRemove.cmake | 15 ++++++++++++++- 7 files changed, 58 insertions(+), 35 deletions(-) diff --git a/src/BasicFileOp.c b/src/BasicFileOp.c index 54b96af..ddbc65c 100644 --- a/src/BasicFileOp.c +++ b/src/BasicFileOp.c @@ -188,7 +188,7 @@ tResult removeReparsePoint(LPCTSTR currentPath) { HANDLE Handle = CreateFile(currentPath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL); - if (Handle == INVALID_HANDLE_VALUE) { + if (Handle == INVALID_HANDLE_VALUE) { // GCOVR_EXCL_BR_LINE return printLastError(_T("Can't get handle to %s"), getReadableFilename(currentPath)); // GCOVR_EXCL_LINE } @@ -205,7 +205,7 @@ tResult removeReparsePoint(LPCTSTR currentPath) { return eError; // GCOVR_EXCL_STOP } - if (CloseHandle(Handle) == 0) { + if (CloseHandle(Handle) == 0) { // GCOVR_EXCL_BR_LINE return printLastError(_T("Can't close handle to %s"), getReadableFilename(currentPath)); // GCOVR_EXCL_LINE } return removeEmptyDirectory(currentPath); @@ -284,7 +284,7 @@ tResult touchSingleFile(LPCTSTR currentPath, tBool createIfMissing) { printOut(_T("Touch file %s\n"), getReadableFilename(currentPath)); } hFile = CreateFile(currentPath, FILE_WRITE_ATTRIBUTES, 0, NULL, createIfMissing ? OPEN_ALWAYS : OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - if (hFile == INVALID_HANDLE_VALUE) { + if (hFile == INVALID_HANDLE_VALUE) { // GCOVR_EXCL_BR_LINE return printLastError(_T("Can't get handle to %s"), getReadableFilename(currentPath)); // GCOVR_EXCL_LINE } else { LPFILETIME pft = ptr_file_time; @@ -295,14 +295,14 @@ tResult touchSingleFile(LPCTSTR currentPath, tBool createIfMissing) { pft = &ft; GetSystemTime(&st); // Converts the current system time to file time format - if (SystemTimeToFileTime(&st, &ft) == 0) { + if (SystemTimeToFileTime(&st, &ft) == 0) { // GCOVR_EXCL_BR_LINE return printLastError(_T("Can't convert system time to file time")); // GCOVR_EXCL_LINE } } - if (SetFileTime(hFile, (LPFILETIME)NULL, pft, pft) == 0) { + if (SetFileTime(hFile, (LPFILETIME)NULL, pft, pft) == 0) { // GCOVR_EXCL_BR_LINE return printLastError(_T("Can't set access and write time of %s"), getReadableFilename(currentPath)); // GCOVR_EXCL_LINE } - if (CloseHandle(hFile) == 0) { + if (CloseHandle(hFile) == 0) { // GCOVR_EXCL_BR_LINE return printLastError(_T("Can't close handle to %s"), getReadableFilename(currentPath)); // GCOVR_EXCL_LINE } } @@ -341,7 +341,7 @@ tResult openFile(LPCTSTR currentPath, HANDLE *handle) { * @return The handle or INVALID_HANDLE_VALUE if open wasn't successful. */ tResult closeFile(LPCTSTR currentPath, HANDLE *handle) { - if (CloseHandle(handle) == 0) { + if (CloseHandle(handle) == 0) { // GCOVR_EXCL_BR_LINE return printLastError(_T("Can't close handle to %s"), getReadableFilename(currentPath)); // GCOVR_EXCL_LINE } @@ -396,13 +396,13 @@ tResult storeTimeValue(int *argc, wchar_t **argv[]) { return printErr(_T("Wrong format for time %s, expected yyyy-mm-dd[Thh:mm[:ss]].\n"), localTime); } lt.wMilliseconds = 0; - if (TzSpecificLocalTimeToSystemTime((TIME_ZONE_INFORMATION *)NULL, <, &st) == 0) { + if (TzSpecificLocalTimeToSystemTime((TIME_ZONE_INFORMATION *)NULL, <, &st) == 0) { // GCOVR_EXCL_BR_LINE return printLastError(_T("Can't convert local time %s to system time"), localTime); // GCOVR_EXCL_LINE } static FILETIME ft; // Converts the current system time to file time format - if (SystemTimeToFileTime(&st, &ft) == 0) { + if (SystemTimeToFileTime(&st, &ft) == 0) { // GCOVR_EXCL_BR_LINE return printLastError(_T("Can't convert system time to file time")); // GCOVR_EXCL_LINE } @@ -438,14 +438,15 @@ tResult runCommandForEachInputLine(int argc, wchar_t *argv[], tResult (*command) do { // Start behind the current content DWORD Offset = _tcslen(IoBufferWideCharacter); - if (FALSE == ReadFile(InHandle, IoBuffer, SIZE_IO_BUFFER - Offset - 1, &BytesRead, NULL)) { - result &= printLastError(_T("Error reading file %s"), DosDevicePathResponseFile); // GCOVR_EXCL_LINE + if (FALSE == ReadFile(InHandle, IoBuffer, SIZE_IO_BUFFER - Offset - 1, &BytesRead, NULL)) { // GCOVR_EXCL_BR_LINE + result &= printLastError(_T("Error reading file %s"), DosDevicePathResponseFile); // GCOVR_EXCL_LINE } if (BytesRead != 0) { // Add a \0 after the read content IoBuffer[BytesRead] = '\0'; // ...and convert it. - if (MultiByteToWideChar(CP_ACP, 0, IoBuffer, -1, &IoBufferWideCharacter[Offset], SIZE_IO_BUFFER - Offset) == 0) { + if (MultiByteToWideChar(CP_ACP, 0, IoBuffer, -1, &IoBufferWideCharacter[Offset], SIZE_IO_BUFFER - Offset) // GCOVR_EXCL_BR_LINE + == 0) { result &= printLastError(_T("Error converting file content of %s"), DosDevicePathResponseFile); // GCOVR_EXCL_LINE } else { LPTSTR PtrStart = IoBufferWideCharacter; @@ -515,7 +516,7 @@ tResult checkUniqueName(void) { } if (result == eOk) { *CurrentFilename = malloc(_tcslen(PtrFilenameStart) * sizeof(TCHAR *)); - if (*CurrentFilename == NULL) { + if (*CurrentFilename == NULL) { // GCOVR_EXCL_BR_LINE result &= printLastError(_T("Can't allocate memory for current filenames")); // GCOVR_EXCL_LINE } _tcscpy(*CurrentFilename, PtrFilenameStart); @@ -530,7 +531,7 @@ tResult checkUniqueNames(int argc, wchar_t *argv[]) { // (needed for freeing the space) KnownFilenames = malloc(ListSize); - if (KnownFilenames == NULL) { + if (KnownFilenames == NULL) { // GCOVR_EXCL_BR_LINE result &= printLastError(_T("Can't allocate memory for list of filenames")); // GCOVR_EXCL_LINE } else { memset(KnownFilenames, 0, ListSize); @@ -540,13 +541,13 @@ tResult checkUniqueNames(int argc, wchar_t *argv[]) { TCHAR **CurrentFilename = KnownFilenames; while (*CurrentFilename != NULL) { free(*CurrentFilename); - if (errno != 0) { + if (errno != 0) { // GCOVR_EXCL_BR_LINE result &= printLastError(_T("Can't free memory of filename")); // GCOVR_EXCL_LINE } ++CurrentFilename; } free(KnownFilenames); - if (errno != 0) { + if (errno != 0) { // GCOVR_EXCL_BR_LINE result &= printLastError(_T("Can't free memory of list of filenames")); // GCOVR_EXCL_LINE } } @@ -569,13 +570,13 @@ tResult printFileToHandle(LPCTSTR currentPath, HANDLE *outHandle) { if (result == eOk) { DWORD BytesRead = 0; do { - if (FALSE == ReadFile(InHandle, IoBuffer, SIZE_IO_BUFFER, &BytesRead, NULL)) { + if (FALSE == ReadFile(InHandle, IoBuffer, SIZE_IO_BUFFER, &BytesRead, NULL)) { // GCOVR_EXCL_BR_LINE result &= printLastError(_T("Error reading file %s"), getReadableFilename(currentPath)); // GCOVR_EXCL_LINE } if (BytesRead != 0) { DWORD BytesWritten = 0; - if (WriteFile(outHandle, IoBuffer, BytesRead, &BytesWritten, NULL) == 0) { - result &= printLastError(_T("Error writing to output handle")); // GCOVR_EXCL_LINE + if (WriteFile(outHandle, IoBuffer, BytesRead, &BytesWritten, NULL) == 0) { // GCOVR_EXCL_BR_LINE + result &= printLastError(_T("Error writing to output handle")); // GCOVR_EXCL_LINE } } } while ((result == eOk) && (BytesRead != 0)); diff --git a/src/OperationCopy.c b/src/OperationCopy.c index d45bddd..9f3b2ce 100644 --- a/src/OperationCopy.c +++ b/src/OperationCopy.c @@ -100,7 +100,7 @@ static tResult copyOperation(void) { StartOfSourceName[-1] = _T('\0'); // close handle to file - if (!FindClose(hFind)) { + if (!FindClose(hFind)) { // GCOVR_EXCL_BR_LINE result &= printLastError(_T("Can't close file search handle.")); // GCOVR_EXCL_LINE } } diff --git a/src/OperationCopy.cmake b/src/OperationCopy.cmake index 3a39e7f..c7e4804 100644 --- a/src/OperationCopy.cmake +++ b/src/OperationCopy.cmake @@ -92,7 +92,7 @@ test_fileop_check_filesystem( test_fileop_check_filesystem( NAME CopyFileList - ARGS copy --force --time 2004-01-02T00:30 ${CMAKE_CURRENT_LIST_DIR}/OperationCopy.h ${CMAKE_CURRENT_LIST_DIR}/OperationCopy.c ${CMAKE_CURRENT_LIST_DIR}/OperationCopy.cmake . + ARGS copy --force --check-unique-names --time 2004-01-02T00:30 ${CMAKE_CURRENT_LIST_DIR}/OperationCopy.h ${CMAKE_CURRENT_LIST_DIR}/OperationCopy.c ${CMAKE_CURRENT_LIST_DIR}/OperationCopy.cmake . MUST_EXIST OperationCopy.c OperationCopy.cmake OperationCopy.h FILE_TIMESTAMP_REGEX "2004-01-02 00:30:00\\.000000000 \\+0000" ) @@ -124,7 +124,7 @@ test_fileop_check_filesystem( test_fileop_check_filesystem( NAME CopyDirectoryRecursiveForce PREPARE_COMMAND "cp -rf --target-directory=. ${CMAKE_CURRENT_LIST_DIR}" - ARGS copy --recursive --force --time 2008-01-02T00:30 --target-directory=. ${CMAKE_CURRENT_LIST_DIR} + ARGS copy -r --force --time 2008-01-02T00:30 --target-directory=. ${CMAKE_CURRENT_LIST_DIR} MUST_EXIST src/OperationCopy.c src/OperationCopy.cmake src/OperationCopy.h FILE_TIMESTAMP_REGEX "2008-01-02 00:30:00\\.000000000 \\+0000" ) @@ -132,7 +132,7 @@ test_fileop_check_filesystem( test_fileop_check_filesystem( NAME CopyDirectoryRecursiveForceProtected PREPARE_COMMAND "cp -rf --target-directory=. ${CMAKE_CURRENT_LIST_DIR} && chmod -R oga-w ." - ARGS copy --recursive --force --time 2009-01-02T00:30 --target-directory=. ${CMAKE_CURRENT_LIST_DIR} + ARGS copy -R -f --time 2009-01-02T00:30 -t . ${CMAKE_CURRENT_LIST_DIR} MUST_EXIST src/OperationCopy.c src/OperationCopy.cmake src/OperationCopy.h FILE_TIMESTAMP_REGEX "2009-01-02 00:30:00\\.000000000 \\+0000" ) @@ -140,7 +140,7 @@ test_fileop_check_filesystem( test_fileop_check_filesystem( NAME CopyFileForceProtected PREPARE_COMMAND "cp -rf --target-directory=. ${CMAKE_CURRENT_LIST_DIR}/OperationCopy.c && chmod -R oga-w OperationCopy.c" - ARGS copy --recursive --force --time 2010-01-02T00:30 --target-directory=. ${CMAKE_CURRENT_LIST_DIR}/OperationCopy.c + ARGS copy --force --time 2010-01-02T00:30 --target-directory=. ${CMAKE_CURRENT_LIST_DIR}/OperationCopy.c MUST_EXIST OperationCopy.c FILE_TIMESTAMP_REGEX "2010-01-02 00:30:00\\.000000000 \\+0000" ) diff --git a/src/OperationMove.c b/src/OperationMove.c index 1cfd659..7db1a2c 100644 --- a/src/OperationMove.c +++ b/src/OperationMove.c @@ -102,9 +102,9 @@ static tResult moveOperation(void) { // recursive move it result &= moveOperation(); } - } while ((result == eOk) && (FindNextFile(hFind, &FindFileData) != 0)); + } while ((result == eOk) && (FindNextFile(hFind, &FindFileData) != 0)); // GCOVR_EXCL_BR_LINE StartOfSourceName[-1] = _T('\0'); - if (result == eOk) { + if (result == eOk) { // GCOVR_EXCL_BR_LINE // We are at the end of the list // GCOVR_EXCL_START if (GetLastError() != ERROR_NO_MORE_FILES) { @@ -114,8 +114,8 @@ static tResult moveOperation(void) { } // close handle to file - if (!FindClose(hFind)) { - result &= printLastError(_T("Can't close file search handle."));// GCOVR_EXCL_LINE + if (!FindClose(hFind)) { // GCOVR_EXCL_BR_LINE + result &= printLastError(_T("Can't close file search handle.")); // GCOVR_EXCL_LINE } removeEmptyDirectory(DosDevicePath); } diff --git a/src/OperationMove.cmake b/src/OperationMove.cmake index 6415224..5291bbc 100644 --- a/src/OperationMove.cmake +++ b/src/OperationMove.cmake @@ -44,7 +44,7 @@ test_fileop_check_filesystem( test_fileop_check_filesystem( NAME MoveFileWithTargetDirectory PREPARE_COMMAND "mkdir SubDir && cp -f ${CMAKE_CURRENT_LIST_FILE} ." - ARGS move --target-directory=SubDir OperationMove.cmake + ARGS move OperationMove.cmake SubDir/ MUST_NOT_EXIST OperationMove.cmake MUST_EXIST SubDir/OperationMove.cmake ) @@ -87,7 +87,7 @@ test_fileop_check_filesystem( test_fileop_check_filesystem( NAME MoveFileListWithPatternExisting PREPARE_COMMAND "mkdir -p Source Target && cp -f ${CMAKE_CURRENT_LIST_DIR}/OperationMove.* Source/ && cp -f Source/*.* Target/" - ARGS move --touch --time=2002-01-01T00:30 Target/OperationMove.* MoveFileListWithPatternTarget/ + ARGS move --touch --check-unique-names --time=2002-01-01T00:30 Target/OperationMove.* MoveFileListWithPatternTarget/ WILL_FAIL MUST_EXIST Source/OperationMove.c Source/OperationMove.cmake Source/OperationMove.h ) @@ -120,7 +120,7 @@ test_fileop_check_filesystem( test_fileop_check_filesystem( NAME MoveDirectoryToExistingFile PREPARE_COMMAND "mkdir SubDir && touch target SubDir/file" - ARGS --debug move --target-directory target SubDir + ARGS --debug move -t target SubDir WILL_FAIL MUST_EXIST target MUST_NOT_EXIST target/SubDir/file @@ -146,8 +146,17 @@ test_fileop_check_filesystem( test_fileop_check_filesystem( NAME MoveDirectoryRecursive + PREPARE_COMMAND "mkdir -p source target/source && cp -rf ${CMAKE_CURRENT_LIST_DIR}/OperationMove.* source/" + ARGS --debug move --force --touch --time 2000-01-01 source target/ + MUST_EXIST target/source/OperationMove.c target/source/OperationMove.cmake target/source/OperationMove.h + MUST_NOT_EXIST source/OperationMove.c source/OperationMove.cmake source/OperationMove.h + FILE_TIMESTAMP_REGEX "2000-01-01 12:00:00\\.000000000 \\+0000" +) + +test_fileop_check_filesystem( + NAME MoveDirectoryRecursiveProtected PREPARE_COMMAND "mkdir -p source target/source && cp -rf ${CMAKE_CURRENT_LIST_DIR}/OperationMove.* source/ && chmod -R oga-w ." - ARGS --debug move --force --touch --time 2000-01-01 source target + ARGS --debug move -f --touch --time 2000-01-01 source target MUST_EXIST target/source/OperationMove.c target/source/OperationMove.cmake target/source/OperationMove.h MUST_NOT_EXIST source/OperationMove.c source/OperationMove.cmake source/OperationMove.h FILE_TIMESTAMP_REGEX "2000-01-01 12:00:00\\.000000000 \\+0000" diff --git a/src/OperationRemove.c b/src/OperationRemove.c index ef5f165..8b83c36 100644 --- a/src/OperationRemove.c +++ b/src/OperationRemove.c @@ -30,7 +30,7 @@ static tResult removeOperation(void) { result &= clearReadonly(DosDevicePath, dwAttrs); } - if (result == eOk) { + if (result == eOk) { // GCOVR_EXCL_BR_LINE if (isReparsePoint(dwAttrs)) { result &= removeReparsePoint(DosDevicePath); } else if (isDirectory(dwAttrs)) { @@ -69,7 +69,7 @@ static tResult removeOperation(void) { StartOfName[-1] = _T('\0'); // close handle to file - if (!FindClose(hFind)) { + if (!FindClose(hFind)) { // GCOVR_EXCL_BR_LINE result &= printLastError(_T("Can't close file search handle.")); // GCOVR_EXCL_LINE } } diff --git a/src/OperationRemove.cmake b/src/OperationRemove.cmake index 3ba2dcc..d6273ae 100644 --- a/src/OperationRemove.cmake +++ b/src/OperationRemove.cmake @@ -3,6 +3,11 @@ test_fileop_command_common( HELP_REGULAR_EXPRESSION "Remove files or directories\\." "Available options are:" ) +test_fileop_check_filesystem( + NAME RemoveNonExistingI + ARGS remove file +) + test_fileop_check_filesystem( NAME RemoveNonExisting ARGS --debug remove file @@ -40,7 +45,15 @@ test_fileop_check_filesystem( test_fileop_check_filesystem( NAME RemoveNotEmptyDirectoryRecursiveForce PREPARE_COMMAND ${REMOVE_PREPARE_COMMAND} - ARGS remove --recursive --force test + ARGS remove -r --force test + MUST_EXIST junction_target/test_file + MUST_NOT_EXIST test/subdir/junction/test_file test/subdir/test_file_1 test/subdir/test_file_3 +) + +test_fileop_check_filesystem( + NAME RemoveNotEmptyDirectoryRecursiveF + PREPARE_COMMAND ${REMOVE_PREPARE_COMMAND} + ARGS remove -R -f test MUST_EXIST junction_target/test_file MUST_NOT_EXIST test/subdir/junction/test_file test/subdir/test_file_1 test/subdir/test_file_3 )