diff --git a/.appveyor.yml b/.appveyor.yml index a5c4f280..dbf6ee22 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,12 +1,13 @@ # Copyright 2016, 2017 Peter Dimov # Copyright 2017 - 2019 James E. King III -# Copyright 2019 - 2020 Alexander Grund +# Copyright 2019 - 2022 Alexander Grund +# # Distributed under the Boost Software License, Version 1.0. -# (See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt) +# https://www.boost.org/LICENSE_1_0.txt version: 1.0.{build}-{branch} -shallow_clone: false +shallow_clone: true branches: only: @@ -27,7 +28,7 @@ environment: global: B2_CI_VERSION: 1 GIT_FETCH_JOBS: 4 - # see: http://www.boost.org/build/doc/html/bbv2/overview/invocation.html#bbv2.overview.invocation.properties + # see: https://www.boost.org/build/doc/html/bbv2/overview/invocation.html#bbv2.overview.invocation.properties # to use the default for a given environment, comment it out; recommend you build debug and release however: # on Windows it is important to exercise all the possibilities, especially shared vs static, however most # libraries that care about this exercise it in their Jamfiles... @@ -37,11 +38,25 @@ environment: B2_VARIANT: release,debug matrix: - - FLAVOR: Visual Studio 2022 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 + - FLAVOR: Visual Studio 2008, 2010, 2012 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + B2_TOOLSET: msvc-9.0,msvc-10.0,msvc-11.0 + B2_ADDRESS_MODEL: 32 # No 64bit support + + - FLAVOR: Visual Studio 2013, 2015 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + B2_TOOLSET: msvc-12.0,msvc-14.0 + + - FLAVOR: Visual Studio 2017 C++14/17 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + B2_CXXSTD: 14,17 + B2_TOOLSET: msvc-14.1 + + - FLAVOR: Visual Studio 2017 C++2a Strict + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 B2_CXXFLAGS: -permissive- - B2_CXXSTD: 14,17,20 - B2_TOOLSET: msvc-14.3 + B2_CXXSTD: 2a + B2_TOOLSET: msvc-14.1 - FLAVOR: Visual Studio 2019 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 @@ -49,16 +64,11 @@ environment: B2_CXXSTD: 14,17,2a B2_TOOLSET: msvc-14.2 - - FLAVOR: Visual Studio 2017 C++2a Strict - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + - FLAVOR: Visual Studio 2022 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 B2_CXXFLAGS: -permissive- - B2_CXXSTD: 2a - B2_TOOLSET: msvc-14.1 - - - FLAVOR: Visual Studio 2017 C++14/17 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - B2_CXXSTD: 14,17 - B2_TOOLSET: msvc-14.1 + B2_CXXSTD: 14,17,20 + B2_TOOLSET: msvc-14.3 - FLAVOR: clang-cl APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 @@ -66,15 +76,6 @@ environment: B2_CXXSTD: 11,14,17 B2_TOOLSET: clang-win - - FLAVOR: Visual Studio 2015, 2013 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - B2_TOOLSET: msvc-12.0,msvc-14.0 - - - FLAVOR: Visual Studio 2008, 2010, 2012 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - B2_TOOLSET: msvc-9.0,msvc-10.0,msvc-11.0 - B2_ADDRESS_MODEL: 32 # No 64bit support - - FLAVOR: cygwin (32-bit) APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 ADDPATH: C:\cygwin\bin; @@ -89,13 +90,22 @@ environment: B2_CXXSTD: 03,11,14,1z B2_TOOLSET: gcc - - FLAVOR: mingw32 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - B2_ADDRESS_MODEL: 32 - ADDPATH: C:\mingw\bin; + # (Currently) the images up to 2017 use an older Cygwin + # This tests that the library works with more recent versions + - FLAVOR: cygwin (64-bit, latest) + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 + ADDPATH: C:\cygwin64\bin; + B2_ADDRESS_MODEL: 64 B2_CXXSTD: 03,11,14,1z B2_TOOLSET: gcc + - FLAVOR: mingw64 (32-bit) + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + ADDPATH: C:\mingw-w64\i686-8.1.0-posix-dwarf-rt_v6-rev0\mingw32\bin; + B2_ADDRESS_MODEL: 32 + B2_CXXSTD: 03,11,14,17,2a + B2_TOOLSET: gcc + - FLAVOR: mingw64 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 ADDPATH: C:\mingw-w64\x86_64-8.1.0-posix-seh-rt_v6-rev0\mingw64\bin; @@ -117,21 +127,25 @@ environment: # CMake builds using preinstalled Boost - CMAKE: true APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + CXXSTD: 14 GENERATOR: Visual Studio 14 2015 Win64 configuration: Debug BOOST_ROOT: C:\Libraries\boost_1_60_0 - CMAKE: true - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - GENERATOR: Visual Studio 16 2019 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 + CXXSTD: 17 + # This test sometimes fails on recent images when using CMake + CMAKE_FLAGS: -DBOOST_NOWIDE_DISABLE_CIN_TEST=ON + GENERATOR: Visual Studio 17 2022 configuration: Debug - BOOST_ROOT: C:\Libraries\boost_1_73_0 + BOOST_ROOT: C:\Libraries\boost_1_83_0 # Coverity - COVERITY: true - # Coverity doesn't really support MSVC 2019 yet - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - GENERATOR: Visual Studio 15 2017 - BOOST_ROOT: C:\Libraries\boost_1_69_0 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + GENERATOR: Visual Studio 16 2019 + configuration: Debug + BOOST_ROOT: C:\Libraries\boost_1_85_0 COVERITY_SCAN_TOKEN: secure: FzhGUr+AR/VOBGUta7dDLMDruolChnvyMSvsM/zLvPY= COVERITY_SCAN_NOTIFICATION_EMAIL: @@ -162,8 +176,8 @@ for: - if exist %INSTALL_DIR%\ (rmdir /S /Q %INSTALL_DIR%) - mkdir __build_cmake_test__ - cd __build_cmake_test__ - - cmake -G "%GENERATOR%" -DCMAKE_INSTALL_PREFIX=%INSTALL_DIR% .. - build_script: cmake --build . --config %configuration% --parallel 4 + - cmake -G "%GENERATOR%" -DCMAKE_CXX_STANDARD=%CXXSTD% %CMAKE_FLAGS% -DCMAKE_INSTALL_PREFIX=%INSTALL_DIR% .. + build_script: cmake --build . --config %configuration% --parallel 4 --target tests test_script: - ctest --output-on-failure -C %configuration% --parallel 4 - ps: | @@ -173,7 +187,7 @@ for: # Build consumer example test - cmake --build . --config %configuration% --target install - del /F /S /Q * - - cmake -DBOOST_NOWIDE_INSTALL_TEST=ON -G "%GENERATOR%" -DCMAKE_PREFIX_PATH=%APPVEYOR_BUILD_FOLDER%\installed ../test/cmake_test + - cmake -DBOOST_NOWIDE_INSTALL_TEST=ON -G "%GENERATOR%" -DCMAKE_CXX_STANDARD=%CXXSTD% %CMAKE_FLAGS% -DCMAKE_PREFIX_PATH=%APPVEYOR_BUILD_FOLDER%\installed ../test/cmake_test - cmake --build . --config %configuration% - ctest --output-on-failure -C %configuration% --parallel 4 @@ -189,21 +203,21 @@ for: - cd build - cmake -G "%GENERATOR%" .. - ps: | - nuget install -ExcludeVersion PublishCoverity - cov-build.exe --dir cov-int cmake --build . --config $env:configuration + cov-configure --msvc + cov-build --dir cov-int cmake --build . --target tests --config $env:configuration If ($LastExitCode -ne 0) { cat cov-int/build-log.txt $host.SetShouldExit($LastExitCode) } - PublishCoverity\tools\PublishCoverity.exe compress --nologo -i cov-int -o cov-int.zip --overwrite - - # This may fail due to a wrong HTTP 500 code from coverity, hence the stderr redirection via cmd - # to make the build succeed anyway - cmd /c PublishCoverity\tools\PublishCoverity.exe publish --nologo ` - -t "$env:COVERITY_SCAN_TOKEN" ` - -e "$env:COVERITY_SCAN_NOTIFICATION_EMAIL" ` - -r "$env:APPVEYOR_REPO_NAME" ` - -z "cov-int.zip" ` - -d "Appveyor build for $env:APPVEYOR_REPO_BRANCH" ` - --codeVersion "$env:APPVEYOR_REPO_BRANCH" 2`>`&1 - Write-Host "Done" + 7z a -tzip cov-int.zip cov-int + curl.exe --fail --silent --show-error ` + --form token="$env:COVERITY_SCAN_TOKEN" ` + --form email="$env:COVERITY_SCAN_NOTIFICATION_EMAIL" ` + --form file=@cov-int.zip ` + --form version="$env:APPVEYOR_REPO_BRANCH" ` + --form description="Appveyor build for $env:APPVEYOR_REPO_BRANCH" ` + https://scan.coverity.com/builds?project=$env:APPVEYOR_REPO_NAME + If ($LastExitCode -ne 0) { + echo "Upload failed" + $host.SetShouldExit($LastExitCode) + } diff --git a/.azure-pipelines.yml b/.azure-pipelines.yml index 7844c75a..4ec1992a 100644 --- a/.azure-pipelines.yml +++ b/.azure-pipelines.yml @@ -3,22 +3,7 @@ # Copyright 2019 Mateusz Loskot # Copyright 2020-2021 Alexander Grund # Distributed under the Boost Software License, Version 1.0. -# (See accompanying file LICENSE_1_0.txt or copy at http://boost.org/LICENSE_1_0.txt) - -# -# Generic Azure Pipelines build script for boostorg repositories -# See: https://github.com/boostorg/boost-ci/ -# -# Instructions for customizing this script for your library: -# -# 1. Customize the compilers and language levels you want. -# 2. If you have more than include/, src/, test/, example/, examples/, -# benchmark/ or tools/ directories, set the environment variable DEPINST. -# For example if your build uses code in "bench/" and "fog/" directories: -# - DEPINST: --include bench --include fog -# 3. Enable pull request builds in your boostorg/ account. -# -# That's it - the script will do everything else for you. +# https://www.boost.org/LICENSE_1_0.txt trigger: branches: @@ -44,44 +29,42 @@ parameters: - name: jobs type: object default: - - { compiler: gcc-11, cxxstd: '14,17,20', os: ubuntu-20.04 } - - { compiler: gcc-10, cxxstd: '14,17,20', os: ubuntu-20.04 } - - { compiler: gcc-9, cxxstd: '14,17,2a', os: ubuntu-20.04 } + - { compiler: gcc-4.8, cxxstd: '11', os: ubuntu-20.04, container: 'ubuntu:16.04' } + - { compiler: gcc-4.9, cxxstd: '11', os: ubuntu-20.04, container: 'ubuntu:16.04' } + - { compiler: gcc-5, cxxstd: '11', os: ubuntu-20.04, container: 'ubuntu:18.04' } + - { compiler: gcc-6, cxxstd: '11,14', os: ubuntu-20.04, container: 'ubuntu:18.04' } + - { compiler: gcc-7, cxxstd: '11,14,17', os: ubuntu-20.04 } - { compiler: gcc-8, cxxstd: '14,17,2a', os: ubuntu-20.04 } - - { compiler: gcc-7, cxxstd: '11,14,17', os: ubuntu-18.04 } - - { compiler: gcc-6, cxxstd: '11,14', os: ubuntu-18.04 } - - { compiler: gcc-5, cxxstd: '11', os: ubuntu-18.04 } - - { compiler: gcc-4.9, cxxstd: '11', os: ubuntu-18.04, container: 'ubuntu:16.04' } - - { compiler: gcc-4.8, cxxstd: '11', os: ubuntu-18.04 } - - { compiler: clang-12, cxxstd: '14,17,20', os: ubuntu-20.04 } - - { compiler: clang-11, cxxstd: '14,17,20', os: ubuntu-20.04 } - - { compiler: clang-10, cxxstd: '14,17,20', os: ubuntu-20.04 } + - { compiler: gcc-9, cxxstd: '14,17,2a', os: ubuntu-20.04 } + - { compiler: gcc-10, cxxstd: '14,17,20', os: ubuntu-20.04 } + - { compiler: gcc-11, cxxstd: '14,17,20', os: ubuntu-20.04 } + - { compiler: clang-3.5, cxxstd: '11', os: ubuntu-20.04, container: 'ubuntu:16.04' } + - { compiler: clang-3.6, cxxstd: '11', os: ubuntu-20.04, container: 'ubuntu:16.04' } + - { compiler: clang-3.7, cxxstd: '11', os: ubuntu-20.04, container: 'ubuntu:16.04' } + - { compiler: clang-3.8, cxxstd: '11,14', os: ubuntu-20.04, container: 'ubuntu:16.04' } + - { compiler: clang-3.9, cxxstd: '11,14', os: ubuntu-20.04, container: 'ubuntu:16.04' } + - { compiler: clang-4.0, cxxstd: '11,14', os: ubuntu-20.04, container: 'ubuntu:16.04' } + - { compiler: clang-5.0, cxxstd: '11,14,17', os: ubuntu-20.04, container: 'ubuntu:16.04' } + - { compiler: clang-6.0, cxxstd: '11,14,17', os: ubuntu-20.04 } + - { compiler: clang-7, cxxstd: '14,17', os: ubuntu-20.04 } + - { compiler: clang-8, cxxstd: '14,17', os: ubuntu-20.04 } - { compiler: clang-9, cxxstd: '14,17,2a', os: ubuntu-20.04 } - - { compiler: clang-8, cxxstd: '14,17', os: ubuntu-18.04, install: 'clang-8 libc6-dbg libc++-dev libstdc++-8-dev' } - - { compiler: clang-7, cxxstd: '14,17', os: ubuntu-18.04, install: 'clang-7 libc6-dbg libc++-dev libstdc++-8-dev' } - - { compiler: clang-6.0, cxxstd: '11,14,17', os: ubuntu-18.04, install: 'clang-6.0 libc6-dbg libc++-dev libc++abi-dev libstdc++-8-dev' } + - { compiler: clang-10, cxxstd: '14,17,20', os: ubuntu-20.04 } + - { compiler: clang-11, cxxstd: '14,17,20', os: ubuntu-20.04 } + - { compiler: clang-14, cxxstd: '14,17,20', os: ubuntu-22.04, gcc_toolchain: 12 } - { name: Linux_clang_6_libcxx, - compiler: clang-6.0, cxxstd: '11,14,17', os: ubuntu-18.04, install: 'clang-6.0 libc6-dbg libc++-dev libc++abi-dev libstdc++-8-dev', env: {B2_STDLIB: libc++ } } - - { compiler: clang-5.0, cxxstd: '11,14,17', os: ubuntu-18.04 } - - { compiler: clang-4.0, cxxstd: '11,14', os: ubuntu-18.04 } - - { compiler: clang-3.9, cxxstd: '11,14', os: ubuntu-18.04 } - - { compiler: clang-3.8, cxxstd: '11,14', os: ubuntu-18.04, container: 'ubuntu:16.04' } - - { compiler: clang-3.7, cxxstd: '11', os: ubuntu-18.04, container: 'ubuntu:16.04' } - - { compiler: clang-3.6, cxxstd: '11', os: ubuntu-18.04, container: 'ubuntu:16.04' } - - { compiler: clang-3.5, cxxstd: '11', os: ubuntu-18.04, container: 'ubuntu:16.04' } + compiler: clang-6.0, cxxstd: '11,14,17', os: ubuntu-20.04, container: 'ubuntu:18.04', install: 'clang-6.0 libc++-dev libc++abi-dev', env: {B2_STDLIB: libc++ } } # OSX - - { compiler: clang, cxxstd: '14,17,2a', os: macOS-10.15, xcode: 12.4 } - - { compiler: clang, cxxstd: '14,17,2a', os: macOS-10.15, xcode: 12.3 } - - { compiler: clang, cxxstd: '14,17,2a', os: macOS-10.15, xcode: 12.2 } - - { compiler: clang, cxxstd: '14,17,2a', os: macOS-10.15, xcode: 12.1.1 } - - { compiler: clang, cxxstd: '14,17,2a', os: macOS-10.15, xcode: 12.0.1 } - - { compiler: clang, cxxstd: '14,17,2a', os: macOS-10.15, xcode: 11.7 } - - { compiler: clang, cxxstd: '14,17,2a', os: macOS-10.15, xcode: 11.6 } - - { compiler: clang, cxxstd: '14,17,2a', os: macOS-10.15, xcode: 11.5 } - - { compiler: clang, cxxstd: '14,17,2a', os: macOS-10.15, xcode: 11.4.1 } - - { compiler: clang, cxxstd: '14,17,2a', os: macOS-10.15, xcode: 11.3.1 } - - { compiler: clang, cxxstd: '14,17,2a', os: macOS-10.15, xcode: 11.2.1 } - - { compiler: clang, cxxstd: '14,17,2a', os: macOS-10.15, xcode: 11.3 } + - { compiler: clang, cxxstd: '14,17,2a', os: macOS-11, xcode: '11.7' } + - { compiler: clang, cxxstd: '14,17,2a', os: macOS-11, xcode: '12.4' } + - { compiler: clang, cxxstd: '14,17,2a', os: macOS-11, xcode: '12.5.1' } + - { compiler: clang, cxxstd: '14,17,2a', os: macOS-11, xcode: '13.0' } + - { compiler: clang, cxxstd: '14,17,2a', os: macOS-12, xcode: '13.1' } + - { compiler: clang, cxxstd: '14,17,2a', os: macOS-12, xcode: '13.2.1' } + - { compiler: clang, cxxstd: '14,17,2a', os: macOS-12, xcode: '13.3.1' } + - { compiler: clang, cxxstd: '14,17,2a', os: macOS-12, xcode: '13.4' } + - { compiler: clang, cxxstd: '14,17,2a', os: macOS-12, xcode: '13.4.1' } + - { compiler: clang, cxxstd: '14,17,2a', os: macOS-12, xcode: '14.0.1' } stages: - stage: Test @@ -111,6 +94,8 @@ stages: XCODE_APP: /Applications/Xcode_${{ item.xcode }}.app ${{ if item.install }}: PACKAGES: ${{ item.install }} + ${{ if item.gcc_toolchain }}: + GCC_TOOLCHAIN: ${{ item.gcc_toolchain }} ${{ each var in item.env }}: ${{var.Key}}: ${{var.Value}} steps: @@ -148,34 +133,15 @@ stages: cd $BOOST_ROOT/libs/$SELF ci/azure-pipelines/build.sh - displayName: 'Build' - - job: 'Windows' + - job: Windows timeoutInMinutes: 120 strategy: matrix: - VS_2022: - B2_TOOLSET: msvc-14.3 - B2_CXXSTD: 14,17,20 - B2_ADDRESS_MODEL: 32,64 - VM_IMAGE: 'windows-2022' - VS_2022_strict: - B2_TOOLSET: msvc-14.3 - B2_CXXSTD: 14,17,20 - B2_CXXFLAGS: -permissive- - B2_ADDRESS_MODEL: 32,64 - VM_IMAGE: 'windows-2022' - VS_2019: - B2_TOOLSET: msvc-14.2 - B2_CXXSTD: 14,17,20 - B2_ADDRESS_MODEL: 32,64 - VM_IMAGE: 'windows-2019' - VS_2019_strict: - B2_TOOLSET: msvc-14.2 - B2_CXXSTD: 14,17,20 - B2_CXXFLAGS: -permissive- - B2_ADDRESS_MODEL: 32,64 - VM_IMAGE: 'windows-2019' + VS_2019: { B2_TOOLSET: msvc-14.2, B2_CXXSTD: '14,17,20', B2_ADDRESS_MODEL: '32,64', VM_IMAGE: windows-2019 } + VS_2019_strict: { B2_TOOLSET: msvc-14.2, B2_CXXSTD: '14,17,20', B2_ADDRESS_MODEL: '32,64', VM_IMAGE: windows-2019, B2_CXXFLAGS: -permissive- } + VS_2022: { B2_TOOLSET: msvc-14.3, B2_CXXSTD: '14,17,20', B2_ADDRESS_MODEL: '32,64', VM_IMAGE: windows-2022 } + VS_2022_strict: { B2_TOOLSET: msvc-14.3, B2_CXXSTD: '14,17,20', B2_ADDRESS_MODEL: '32,64', VM_IMAGE: windows-2022, B2_CXXFLAGS: -permissive- } pool: vmImage: $(VM_IMAGE) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..5933e5ac --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: Flamefire diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e0113d82..303bcd24 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,10 +1,12 @@ # Copyright 2020-2021 Peter Dimov # Copyright 2021 Andrey Semashev -# Copyright 2021 Alexander Grund +# Copyright 2021-2024 Alexander Grund +# Copyright 2022 James E. King III # # Distributed under the Boost Software License, Version 1.0. -# (See accompanying file LICENSE_1_0.txt or copy at http://boost.org/LICENSE_1_0.txt) +# https://www.boost.org/LICENSE_1_0.txt +--- name: CI on: @@ -13,7 +15,10 @@ on: branches: - master - develop + - bugfix/** - feature/** + - fix/** + - pr/** concurrency: group: ${{format('{0}:{1}', github.repository, github.ref)}} @@ -39,51 +44,71 @@ jobs: matrix: include: # Linux, gcc - - { compiler: gcc-4.4, cxxstd: '98,0x', os: ubuntu-20.04, container: 'ubuntu:16.04' } - - { compiler: gcc-4.6, cxxstd: '03,0x', os: ubuntu-20.04, container: 'ubuntu:16.04' } - - { compiler: gcc-4.7, cxxstd: '03,11', os: ubuntu-20.04, container: 'ubuntu:16.04' } - - { compiler: gcc-4.8, cxxstd: '03,11', os: ubuntu-18.04 } - - { compiler: gcc-4.9, cxxstd: '03,11', os: ubuntu-20.04, container: 'ubuntu:16.04' } - - { compiler: gcc-5, cxxstd: '03,11,14,1z', os: ubuntu-18.04 } - - { compiler: gcc-6, cxxstd: '03,11,14,17', os: ubuntu-18.04 } - - { compiler: gcc-7, cxxstd: '03,11,14,17', os: ubuntu-18.04 } - - { compiler: gcc-8, cxxstd: '03,11,14,17,2a', os: ubuntu-18.04 } - - { compiler: gcc-9, cxxstd: '03,11,14,17,2a', os: ubuntu-18.04 } - - { compiler: gcc-10, cxxstd: '03,11,14,17,20', os: ubuntu-20.04 } - - { compiler: gcc-11, cxxstd: '03,11,14,17,20', os: ubuntu-20.04 } + - { compiler: gcc-4.4, cxxstd: '98,0x', os: ubuntu-latest, container: 'ubuntu:16.04' } + - { compiler: gcc-4.6, cxxstd: '03,0x', os: ubuntu-latest, container: 'ubuntu:16.04' } + - { compiler: gcc-4.7, cxxstd: '03,11', os: ubuntu-latest, container: 'ubuntu:16.04' } + - { compiler: gcc-4.8, cxxstd: '03,11', os: ubuntu-latest, container: 'ubuntu:16.04' } + - { compiler: gcc-4.9, cxxstd: '03,11', os: ubuntu-latest, container: 'ubuntu:16.04' } + - { compiler: gcc-5, cxxstd: '03,11,14,1z', os: ubuntu-latest, container: 'ubuntu:18.04' } + - { compiler: gcc-6, cxxstd: '11,14,17', os: ubuntu-latest, container: 'ubuntu:18.04' } + - { compiler: gcc-7, cxxstd: '11,14,17', os: ubuntu-20.04 } + - { compiler: gcc-8, cxxstd: '11,14,17,2a', os: ubuntu-20.04 } + - { compiler: gcc-9, cxxstd: '11,14,17,2a', os: ubuntu-20.04 } + - { compiler: gcc-10, cxxstd: '11,14,17,20', os: ubuntu-22.04 } + - { compiler: gcc-11, cxxstd: '11,14,17,20', os: ubuntu-22.04 } + - { compiler: gcc-12, cxxstd: '11,14,17,20', os: ubuntu-22.04 } + - { compiler: gcc-13, cxxstd: '11,14,17,20,2b', os: ubuntu-24.04 } + - { compiler: gcc-14, cxxstd: '11,14,17,20,2b', os: ubuntu-24.04 } + - { name: GCC w/ sanitizers, sanitize: yes, - compiler: gcc-11, cxxstd: '03,11,14,17,20', os: ubuntu-20.04 } + compiler: gcc-13, cxxstd: '11,14,17,20', os: ubuntu-24.04 } - { name: Collect coverage, coverage: yes, - compiler: gcc-8, cxxstd: '03,11', os: ubuntu-20.04, install: 'g++-8-multilib', address-model: '32,64' } + compiler: gcc-8, cxxstd: '11,14,17,2a', os: ubuntu-20.04, install: 'g++-8-multilib', address-model: '32,64' } # Linux, clang - - { compiler: clang-3.5, cxxstd: '03,11', os: ubuntu-20.04, container: 'ubuntu:16.04' } - - { compiler: clang-3.6, cxxstd: '03,11,14', os: ubuntu-20.04, container: 'ubuntu:16.04' } - - { compiler: clang-3.7, cxxstd: '03,11,14', os: ubuntu-20.04, container: 'ubuntu:16.04' } - - { compiler: clang-3.8, cxxstd: '03,11,14', os: ubuntu-20.04, container: 'ubuntu:16.04' } - - { compiler: clang-3.9, cxxstd: '03,11,14', os: ubuntu-18.04 } - - { compiler: clang-4.0, cxxstd: '03,11,14', os: ubuntu-18.04 } - - { compiler: clang-5.0, cxxstd: '03,11,14,1z', os: ubuntu-18.04 } - - { compiler: clang-6.0, cxxstd: '03,11,14,17', os: ubuntu-18.04 } - - { compiler: clang-7, cxxstd: '03,11,14,17', os: ubuntu-18.04 } - # Note: clang-8 does not fully support C++20, so it is not compatible with some libstdc++ versions in this mode - - { compiler: clang-8, cxxstd: '03,11,14,17,2a', os: ubuntu-18.04, install: 'clang-8 g++-7', gcc_toolchain: 7 } - - { compiler: clang-9, cxxstd: '03,11,14,17,2a', os: ubuntu-20.04 } - - { compiler: clang-10, cxxstd: '03,11,14,17,20', os: ubuntu-20.04 } - - { compiler: clang-11, cxxstd: '03,11,14,17,20', os: ubuntu-20.04 } - - { compiler: clang-12, cxxstd: '03,11,14,17,20', os: ubuntu-20.04 } - # libc++ - - { compiler: clang-6.0, cxxstd: '03,11,14', os: ubuntu-18.04, stdlib: libc++, install: 'clang-6.0 libc++-dev libc++abi-dev' } - - { compiler: clang-12, cxxstd: '03,11,14,17,20', os: ubuntu-20.04, stdlib: libc++, install: 'clang-12 libc++-12-dev libc++abi-12-dev' } + - { compiler: clang-3.5, cxxstd: '11', os: ubuntu-latest, container: 'ubuntu:16.04' } + - { compiler: clang-3.6, cxxstd: '11,14', os: ubuntu-latest, container: 'ubuntu:16.04' } + - { compiler: clang-3.7, cxxstd: '11,14', os: ubuntu-latest, container: 'ubuntu:16.04' } + - { compiler: clang-3.8, cxxstd: '11,14', os: ubuntu-latest, container: 'ubuntu:16.04' } + - { compiler: clang-3.9, cxxstd: '11,14', os: ubuntu-latest, container: 'ubuntu:18.04' } + - { compiler: clang-4.0, cxxstd: '11,14', os: ubuntu-latest, container: 'ubuntu:18.04' } + - { compiler: clang-5.0, cxxstd: '11,14,1z', os: ubuntu-latest, container: 'ubuntu:18.04' } + - { compiler: clang-6.0, cxxstd: '11,14,17', os: ubuntu-20.04 } + - { compiler: clang-7, cxxstd: '11,14,17', os: ubuntu-20.04 } + # Note: clang-8 does not fully support C++20, so it is not compatible with some libstdc++ versions in this mode + - { compiler: clang-8, cxxstd: '11,14,17,2a', os: ubuntu-20.04 , install: 'clang-8 g++-7', gcc_toolchain: 7 } + - { compiler: clang-9, cxxstd: '11,14,17,2a', os: ubuntu-20.04 } + - { compiler: clang-10, cxxstd: '11,14,17,20', os: ubuntu-20.04 } + - { compiler: clang-11, cxxstd: '11,14,17,20', os: ubuntu-20.04 } + - { compiler: clang-12, cxxstd: '11,14,17,20', os: ubuntu-20.04 } + # Clang isn't compatible with libstdc++-13, so use the slightly older one + - { compiler: clang-13, cxxstd: '11,14,17,20', os: ubuntu-22.04, install: 'clang-13 g++-12', gcc_toolchain: 12 } + - { compiler: clang-14, cxxstd: '11,14,17,20', os: ubuntu-22.04, install: 'clang-14 g++-12', gcc_toolchain: 12 } + - { compiler: clang-15, cxxstd: '11,14,17,20', os: ubuntu-22.04, install: 'clang-15 g++-12', gcc_toolchain: 12 } + - { compiler: clang-16, cxxstd: '11,14,17,20,2b', os: ubuntu-24.04 } + # https://github.com/llvm/llvm-project/issues/59827: disabled 2b/23 for clang-17 with libstdc++13 in 24.04 + - { compiler: clang-17, cxxstd: '11,14,17,20', os: ubuntu-24.04 } + - { compiler: clang-18, cxxstd: '11,14,17,20,23,2c', os: ubuntu-24.04 } + + # libc++ + - { compiler: clang-6.0, cxxstd: '11,14', os: ubuntu-latest, container: 'ubuntu:18.04', stdlib: libc++, install: 'clang-6.0 libc++-dev libc++abi-dev' } + - { compiler: clang-12, cxxstd: '11,14,17,20', os: ubuntu-20.04, stdlib: libc++, install: 'clang-12 libc++-12-dev libc++abi-12-dev' } - { name: Clang w/ sanitizers, sanitize: yes, - compiler: clang-12, cxxstd: '03,11,14,17,20', os: ubuntu-20.04, stdlib: libc++, install: 'clang-12 libc++-12-dev libc++abi-12-dev' } + compiler: clang-14, cxxstd: '11,14,17,20', os: ubuntu-22.04, stdlib: libc++, install: 'clang-14 libc++-14-dev libc++abi-14-dev' } # OSX, clang - - { compiler: clang, cxxstd: '03,11,14,17,2a', os: macos-10.15, sanitize: yes } + - { name: MacOS w/ clang and sanitizers, + compiler: clang, cxxstd: '11,14,17,20,2b', os: macos-13, sanitize: yes } + - { compiler: clang, cxxstd: '11,14,17,20,2b', os: macos-14 } + - { compiler: clang, cxxstd: '11,14,17,20,2b', os: macos-15 } timeout-minutes: 120 runs-on: ${{matrix.os}} - container: ${{matrix.container}} + container: + image: ${{matrix.container}} + volumes: + - /node20217:/node20217:rw,rshared + - ${{ startsWith(matrix.container, 'ubuntu:1') && '/node20217:/__e/node20:ro,rshared' || ' ' }} env: {B2_USE_CCACHE: 1} steps: @@ -95,34 +120,54 @@ jobs: fi if [ -n "${{matrix.container}}" ] && [ -f "/etc/debian_version" ]; then apt-get -o Acquire::Retries=$NET_RETRY_COUNT update - apt-get -o Acquire::Retries=$NET_RETRY_COUNT install -y sudo software-properties-common - # Need (newer) git + apt-get -o Acquire::Retries=$NET_RETRY_COUNT install -y sudo software-properties-common curl + # Need (newer) git, and the older Ubuntu container may require requesting the key manually using port 80 + curl -sSL --retry ${NET_RETRY_COUNT:-5} 'http://keyserver.ubuntu.com/pks/lookup?op=get&search=0xE1DD270288B4E6030699E45FA1715D88E1DF1F24' | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/git-core_ubuntu_ppa.gpg for i in {1..${NET_RETRY_COUNT:-3}}; do sudo -E add-apt-repository -y ppa:git-core/ppa && break || sleep 10; done apt-get -o Acquire::Retries=$NET_RETRY_COUNT update - apt-get -o Acquire::Retries=$NET_RETRY_COUNT install -y g++ python libpython-dev git + osver=$(lsb_release -sr | cut -f1 -d.) + pkgs="g++ git" + # Ubuntu 22+ has only Python 3 in the repos + if [ -n "$osver" ] && [ "$osver" -ge "22" ]; then + pkgs+=" python-is-python3 libpython3-dev" + else + pkgs+=" python libpython-dev" + fi + apt-get -o Acquire::Retries=$NET_RETRY_COUNT install -y $pkgs + fi + # For jobs not compatible with ccache, use "ccache: no" in the matrix + if [[ "${{ matrix.ccache }}" == "no" ]]; then + echo "B2_USE_CCACHE=0" >> $GITHUB_ENV fi git config --global pack.threads 0 + if [[ "${{matrix.container}}" == "ubuntu:1"* ]]; then + # Node 20 doesn't work with Ubuntu 16/18 glibc: https://github.com/actions/checkout/issues/1590 + curl -sL https://archives.boost.io/misc/node/node-v20.9.0-linux-x64-glibc-217.tar.xz | tar -xJ --strip-components 1 -C /node20217 + fi ! command -v cmake &> /dev/null || echo "B2_FLAGS=--nowide-enable-cmake" >> $GITHUB_ENV - - uses: actions/checkout@v2 - if: '!matrix.coverage' - - uses: actions/checkout@v2 - if: 'matrix.coverage' + - uses: actions/checkout@v4 with: - fetch-depth: 0 + # For coverage builds fetch the whole history, else only 1 commit using a 'fake ternary' + fetch-depth: ${{ matrix.coverage && '0' || '1' }} - name: Cache ccache - uses: actions/cache@v2 + uses: actions/cache@v4 + if: env.B2_USE_CCACHE with: path: ~/.ccache - key: ${{matrix.os}}-${{matrix.container}}-${{matrix.compiler}} + key: ${{matrix.os}}-${{matrix.container}}-${{matrix.compiler}}-${{github.sha}} + restore-keys: | + ${{matrix.os}}-${{matrix.container}}-${{matrix.compiler}}- + ${{matrix.os}}-${{matrix.container}}-${{matrix.compiler}} - name: Fetch Boost.CI - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: repository: boostorg/boost-ci ref: master path: boost-ci-cloned + - name: Get CI scripts folder run: | # Copy ci folder if not testing Boost.CI @@ -138,7 +183,8 @@ jobs: SOURCES+=(ppa:ubuntu-toolchain-r/test) for key in "${SOURCE_KEYS[@]}"; do for i in {1..$NET_RETRY_COUNT}; do - wget -O - "$key" | sudo apt-key add - && break || sleep 10 + keyfilename=$(basename -s .key $key) + curl -sSL --retry ${NET_RETRY_COUNT:-5} "$key" | sudo gpg --dearmor > /etc/apt/trusted.gpg.d/${keyfilename} && break || sleep 10 done done for source in "${SOURCES[@]}"; do @@ -160,6 +206,9 @@ jobs: run: | GCC_TOOLCHAIN_ROOT="$HOME/gcc-toolchain" echo "GCC_TOOLCHAIN_ROOT=$GCC_TOOLCHAIN_ROOT" >> $GITHUB_ENV + if ! command -v dpkg-architecture; then + apt-get install -y dpkg-dev + fi MULTIARCH_TRIPLET="$(dpkg-architecture -qDEB_HOST_MULTIARCH)" mkdir -p "$GCC_TOOLCHAIN_ROOT" ln -s /usr/include "$GCC_TOOLCHAIN_ROOT/include" @@ -167,6 +216,18 @@ jobs: mkdir -p "$GCC_TOOLCHAIN_ROOT/lib/gcc/$MULTIARCH_TRIPLET" ln -s "/usr/lib/gcc/$MULTIARCH_TRIPLET/${{matrix.gcc_toolchain}}" "$GCC_TOOLCHAIN_ROOT/lib/gcc/$MULTIARCH_TRIPLET/${{matrix.gcc_toolchain}}" + - name: Setup multiarch + if: matrix.multiarch + run: | + sudo apt-get install --no-install-recommends -y binfmt-support qemu-user-static + sudo docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + git clone https://github.com/jeking3/bdde.git + echo "$(pwd)/bdde/bin/linux" >> ${GITHUB_PATH} + echo "BDDE_DISTRO=${{ matrix.distro }}" >> ${GITHUB_ENV} + echo "BDDE_EDITION=${{ matrix.edition }}" >> ${GITHUB_ENV} + echo "BDDE_ARCH=${{ matrix.arch }}" >> ${GITHUB_ENV} + echo "B2_WRAPPER=bdde" >> ${GITHUB_ENV} + - name: Setup Boost env: B2_ADDRESS_MODEL: ${{matrix.address-model}} @@ -181,6 +242,7 @@ jobs: run: ci/github/codecov.sh "setup" - name: Run tests + if: '!matrix.coverity' run: ci/build.sh - name: Run tests with simulated no LFS support @@ -188,9 +250,84 @@ jobs: B2_FLAGS: boost.nowide.lfs=no run: ci/build.sh - - name: Upload coverage + - name: Collect coverage if: matrix.coverage run: ci/codecov.sh "upload" + env: + BOOST_CI_CODECOV_IO_UPLOAD: skip + + - name: Upload coverage + if: matrix.coverage + uses: codecov/codecov-action@v5 + with: + disable_search: true + file: coverage.info + name: Github Actions + token: ${{secrets.CODECOV_TOKEN}} + verbose: true + + - name: Run coverity + if: matrix.coverity && github.event_name == 'push' && (github.ref_name == 'develop' || github.ref_name == 'master') + run: ci/github/coverity.sh + env: + COVERITY_SCAN_NOTIFICATION_EMAIL: ${{ secrets.COVERITY_SCAN_NOTIFICATION_EMAIL }} + COVERITY_SCAN_TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} + + MSYS2: + defaults: + run: + shell: msys2 {0} + strategy: + fail-fast: false + matrix: + include: + - { sys: MINGW32, compiler: gcc, cxxstd: '03,11,17,20' } + - { sys: MINGW64, compiler: gcc, cxxstd: '03,11,17,20' } + + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup MSYS2 environment + uses: msys2/setup-msys2@v2 + with: + msystem: ${{matrix.sys}} + update: true + install: git python + pacboy: gcc:p cmake:p ninja:p + + - name: Fetch Boost.CI + uses: actions/checkout@v4 + with: + repository: boostorg/boost-ci + ref: master + path: boost-ci-cloned + - name: Get CI scripts folder + run: | + # Copy ci folder if not testing Boost.CI + [[ "$GITHUB_REPOSITORY" =~ "boost-ci" ]] || cp -r boost-ci-cloned/ci . + rm -rf boost-ci-cloned + + - name: Setup Boost + env: + B2_COMPILER: ${{matrix.compiler}} + B2_CXXSTD: ${{matrix.cxxstd}} + B2_SANITIZE: ${{matrix.sanitize}} + B2_STDLIB: ${{matrix.stdlib}} + run: ci/github/install.sh + + - name: Run tests + run: ci/build.sh + + # Run also the CMake tests to avoid having to setup another matrix for CMake on MSYS + - name: Run CMake tests + run: | + cd "$BOOST_ROOT" + mkdir __build_cmake_test__ && cd __build_cmake_test__ + cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug -DBOOST_INCLUDE_LIBRARIES=$SELF -DBUILD_SHARED_LIBS=ON -DBUILD_TESTING=ON -DBoost_VERBOSE=ON .. + cmake --build . --target tests --config Debug -j$B2_JOBS + ctest --output-on-failure --build-config Debug CMake: defaults: @@ -210,9 +347,9 @@ jobs: runs-on: ${{matrix.os}} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Fetch Boost.CI - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: repository: boostorg/boost-ci ref: master @@ -231,7 +368,7 @@ jobs: cd "$BOOST_ROOT" mkdir __build_cmake_test__ && cd __build_cmake_test__ cmake -G "${{matrix.generator}}" -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DBOOST_INCLUDE_LIBRARIES=$SELF -DBUILD_SHARED_LIBS=${{matrix.build_shared}} -DBUILD_TESTING=ON -DBoost_VERBOSE=ON .. - cmake --build . --target tests --config ${{matrix.build_type}} + cmake --build . --target tests --config ${{matrix.build_type}} -j$B2_JOBS ctest --output-on-failure --build-config ${{matrix.build_type}} - name: Run CMake subdir tests @@ -241,7 +378,7 @@ jobs: cd "$cmake_test_folder" mkdir __build_cmake_subdir_test__ && cd __build_cmake_subdir_test__ cmake -G "${{matrix.generator}}" -DBOOST_CI_INSTALL_TEST=OFF -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DBUILD_SHARED_LIBS=${{matrix.build_shared}} .. - cmake --build . --config ${{matrix.build_type}} + cmake --build . --config ${{matrix.build_type}} -j$B2_JOBS ctest --output-on-failure --build-config ${{matrix.build_type}} - name: Install Library @@ -249,7 +386,7 @@ jobs: cd "$BOOST_ROOT" mkdir __build_cmake_install_test__ && cd __build_cmake_install_test__ cmake -G "${{matrix.generator}}" -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DBOOST_INCLUDE_LIBRARIES=$SELF -DBUILD_SHARED_LIBS=${{matrix.build_shared}} -DCMAKE_INSTALL_PREFIX=~/.local -DBoost_VERBOSE=ON -DBoost_DEBUG=ON .. - cmake --build . --target install --config ${{matrix.build_type}} + cmake --build . --target install --config ${{matrix.build_type}} -j$B2_JOBS - name: Run CMake install tests run: | cmake_test_folder="$BOOST_ROOT/libs/$SELF/test/cmake_test" # New unified folder @@ -257,5 +394,5 @@ jobs: cd "$cmake_test_folder" mkdir __build_cmake_install_test__ && cd __build_cmake_install_test__ cmake -G "${{matrix.generator}}" -DBOOST_CI_INSTALL_TEST=ON -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DBUILD_SHARED_LIBS=${{matrix.build_shared}} -DCMAKE_PREFIX_PATH=~/.local .. - cmake --build . --config ${{matrix.build_type}} + cmake --build . --config ${{matrix.build_type}} -j$B2_JOBS ctest --output-on-failure --build-config ${{matrix.build_type}} diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index 4a01dc37..b41337a6 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -1,6 +1,6 @@ -# Copyright 2019 - 2021 Alexander Grund +# Copyright 2019 - 2024 Alexander Grund # Distributed under the Boost Software License, Version 1.0. -# (See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt) +# https://www.boost.org/LICENSE_1_0.txt name: CI Tests @@ -10,7 +10,10 @@ on: branches: - master - develop + - bugfix/** - feature/** + - fix/** + - pr/** concurrency: group: ${{format('ci_tests{0}:{1}', github.repository, github.ref)}} @@ -28,27 +31,27 @@ jobs: shell: bash strategy: matrix: - os: [ubuntu-18.04, windows-latest] + os: [ubuntu-22.04, windows-2019] buildType: [Debug, Release] standalone: [Boost, Standalone] shared_lib: [ON, OFF] generator: ['Visual Studio 16 2019', 'MinGW Makefiles', 'Unix Makefiles'] exclude: - - os: ubuntu-18.04 + - os: ubuntu-22.04 generator: MinGW Makefiles - - os: ubuntu-18.04 + - os: ubuntu-22.04 generator: Visual Studio 16 2019 - - os: ubuntu-18.04 + - os: ubuntu-22.04 buildType: Debug runs-on: ${{matrix.os}} env: DEP_DIR: ${{github.workspace}}/dependencies BOOST_VERSION: 1.56.0 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Sanity check version run: grep -E 'set\(_version [0-9]' CMakeLists.txt - - uses: actions/cache@v1 + - uses: actions/cache@v4 id: cache-boost with: path: ${{env.DEP_DIR}} @@ -61,7 +64,7 @@ jobs: if: matrix.standalone == 'Boost' run: echo "BOOST_ROOT=${DEP_DIR//\\/\/}/boost_${BOOST_VERSION//./_}" >> $GITHUB_ENV # Install Boost - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 if: matrix.standalone == 'Boost' && steps.cache-boost.outputs.cache-hit != 'true' with: repository: boostorg/boost @@ -92,19 +95,31 @@ jobs: mkdir build - name: Configure working-directory: build - run: cmake -DBoost_DEBUG=ON -DCMAKE_BUILD_TYPE=${{matrix.buildType}} -DBUILD_SHARED_LIBS=${{matrix.shared_lib}} -DCMAKE_INSTALL_PREFIX=~/.local -G "${{matrix.generator}}" -DBoost_NO_BOOST_CMAKE=ON .. - - name: Build & Install - run: cmake --build build --config ${{matrix.buildType}} --target install + run: | + extraFlags="-DBoost_DEBUG=ON -DBoost_NO_BOOST_CMAKE=ON -DCMAKE_INSTALL_PREFIX=~/.local" + if ! [[ "${{matrix.generator}}" =~ "Visual Studio" ]]; then + # Enable warning to find missing defines, especially important for the standalone version + extraFlags="$extraFlags -DCMAKE_CXX_FLAGS=-Wundef" + fi + cmake -DCMAKE_BUILD_TYPE=${{matrix.buildType}} -DBUILD_SHARED_LIBS=${{matrix.shared_lib}} -G "${{matrix.generator}}" $extraFlags .. + - name: Build + run: cmake --build build --config ${{matrix.buildType}} --target tests # Run test with both bash and powershell and watch for "Using std::cin" on bash but not on powershell - name: Test working-directory: build - run: ctest --output-on-failure -C ${{matrix.buildType}} --verbose + run: | + # The bash shell adds an incompatible PATH for MinGW: https://github.com/actions/runner-images/issues/11102 + [[ "${{runner.os}}" != 'Windows' ]] || export PATH="/c/mingw64/bin:$PATH" + ctest --output-on-failure -C ${{matrix.buildType}} --verbose - name: Test on PowerShell working-directory: build shell: powershell if: runner.os == 'Windows' run: ctest --output-on-failure -C ${{matrix.buildType}} --verbose + + - name: Install + run: cmake --build build --config ${{matrix.buildType}} --target install - name: Test consumption working-directory: build run: | @@ -116,7 +131,7 @@ jobs: CreateDocuTest: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Create documentation run: | sudo apt-get install -y doxygen @@ -125,9 +140,9 @@ jobs: CreateBoostDocuTest: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Fetch Boost.CI - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: repository: boostorg/boost-ci ref: master @@ -144,8 +159,8 @@ jobs: CheckFormatting: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: DoozyX/clang-format-lint-action@v0.11 + - uses: actions/checkout@v4 + - uses: DoozyX/clang-format-lint-action@v0.18 with: exclude: './doc' clangFormatVersion: 10 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 53ceb83b..c4a9afe4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,6 +1,6 @@ # Copyright 2019 - 2020 Alexander Grund # Distributed under the Boost Software License, Version 1.0. -# (See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt) +# https://www.boost.org/LICENSE_1_0.txt on: push: @@ -18,7 +18,7 @@ jobs: DEP_DIR: ${{github.workspace}}/dependencies BOOST_VERSION: 1.56.0 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Extract tag name id: get_tag run: | @@ -43,10 +43,23 @@ jobs: sudo apt-get install doxygen doc/gendoc.sh tar -czf documentation.tar.gz doc index.html + - name: Create standalone version run: | bash tools/create_standalone.sh nowide_standalone_${{steps.get_tag.outputs.tag}} tar -czf nowide_standalone.tar.gz nowide_standalone_${{steps.get_tag.outputs.tag}} + - name: Test standalone release tarball + run: | + tmp_dir=$(mktemp -d -p "$RUNNER_TEMP") + cd "$tmp_dir" + tar -xf "${{github.workspace}}/nowide_standalone.tar.gz" + src_dir="$PWD/nowide_standalone_${{steps.get_tag.outputs.tag}}" + mkdir build && cd build + cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=${{runner.workspace}}/../install "$src_dir" + cmake --build . --config Debug --target tests + ctest --output-on-failure -C Debug --verbose + cmake --build . --config Debug --target install + - name: Create Boost version run: | FOLDER="nowide_${{steps.get_tag.outputs.tag}}" @@ -63,8 +76,10 @@ jobs: src_dir="$PWD/nowide_${{steps.get_tag.outputs.tag}}" mkdir build && cd build cmake -DBoost_DEBUG=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=${{runner.workspace}}/../install "$src_dir" - cmake --build . --config Debug --target install + cmake --build . --config Debug --target tests ctest --output-on-failure -C Debug --verbose + cmake --build . --config Debug --target install + - name: Create Release if: github.event_name == 'push' id: create_release diff --git a/.github/workflows/update_standalone.yml b/.github/workflows/update_standalone.yml index 6e4c45e7..4adb99b1 100644 --- a/.github/workflows/update_standalone.yml +++ b/.github/workflows/update_standalone.yml @@ -1,6 +1,6 @@ # Copyright 2019 - 2021 Alexander Grund # Distributed under the Boost Software License, Version 1.0. -# (See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt) +# https://www.boost.org/LICENSE_1_0.txt on: push: @@ -16,7 +16,7 @@ jobs: name: Update standalone branch runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup git run: | git config --global user.email "$GITHUB_ACTOR@users.noreply.github.com" diff --git a/CMakeLists.txt b/CMakeLists.txt index e1d9d1e2..f6b2bae3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ # Copyright 2019 - 2020 Alexander Grund # Distributed under the Boost Software License, Version 1.0. -# (See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt) +# https://www.boost.org/LICENSE_1_0.txt # Builds the libraries for Boost.Nowide # @@ -15,7 +15,7 @@ cmake_minimum_required(VERSION 3.9) # Version number starts at 10 to avoid conflicts with Boost version -set(_version 11.1.3) +set(_version 11.3.0) if(BOOST_SUPERPROJECT_SOURCE_DIR) set(_version ${BOOST_SUPERPROJECT_VERSION}) endif() diff --git a/README.md b/README.md index 1be035a7..c8c6731b 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # Boost.Nowide -Branch | Appveyor | Github | codecov.io | Documentation -------------|----------|--------|------------|-------------- -[master](https://github.com/boostorg/nowide/tree/master) | [![Build status](https://ci.appveyor.com/api/projects/status/w5sywrekwd66say4/branch/master?svg=true)](https://ci.appveyor.com/project/Flamefire/nowide-fr98b/branch/master) | ![](https://github.com/boostorg/nowide/workflows/CI%20Tests/badge.svg?branch=master) ![](https://github.com/boostorg/nowide/workflows/POSIX/badge.svg?branch=master) | [![codecov](https://codecov.io/gh/boostorg/nowide/branch/master/graph/badge.svg)](https://codecov.io/gh/boostorg/nowide/branch/master) | [![Documentation](https://img.shields.io/badge/documentation-master-brightgreen.svg)](https://www.boost.org/doc/libs/master/libs/nowide/index.html) -[develop](https://github.com/boostorg/nowide/tree/develop) | [![Build status](https://ci.appveyor.com/api/projects/status/w5sywrekwd66say4/branch/develop?svg=true)](https://ci.appveyor.com/project/Flamefire/nowide-fr98b/branch/develop) | ![](https://github.com/boostorg/nowide/workflows/CI%20Tests/badge.svg?branch=develop) ![](https://github.com/boostorg/nowide/workflows/POSIX/badge.svg?branch=develop) | [![codecov](https://codecov.io/gh/boostorg/nowide/branch/develop/graph/badge.svg)](https://codecov.io/gh/boostorg/nowide/branch/develop) | [![Documentation](https://img.shields.io/badge/documentation-develop-brightgreen.svg)](https://www.boost.org/doc/libs/develop/libs/nowide/index.html) +Branch | Appveyor | Github | codecov.io | Deps | Docs | Tests | +------------|----------|--------|------------| ---- | ---- | ----- | +[master](https://github.com/boostorg/nowide/tree/master) | [![Build status](https://ci.appveyor.com/api/projects/status/w5sywrekwd66say4/branch/master?svg=true)](https://ci.appveyor.com/project/Flamefire/nowide-fr98b/branch/master) | ![](https://github.com/boostorg/nowide/workflows/CI%20Tests/badge.svg?branch=master) ![](https://github.com/boostorg/nowide/workflows/POSIX/badge.svg?branch=master) | [![codecov](https://codecov.io/gh/boostorg/nowide/branch/master/graph/badge.svg)](https://codecov.io/gh/boostorg/nowide/branch/master) | [![Deps](https://img.shields.io/badge/deps-master-brightgreen.svg)](https://pdimov.github.io/boostdep-report/master/nowide.html) | [![Documentation](https://img.shields.io/badge/docs-master-brightgreen.svg)](https://www.boost.org/doc/libs/master/libs/nowide/index.html) | [![Enter the Matrix](https://img.shields.io/badge/matrix-master-brightgreen.svg)](https://www.boost.org/development/tests/master/developer/nowide.html) +[develop](https://github.com/boostorg/nowide/tree/develop) | [![Build status](https://ci.appveyor.com/api/projects/status/w5sywrekwd66say4/branch/develop?svg=true)](https://ci.appveyor.com/project/Flamefire/nowide-fr98b/branch/develop) | ![](https://github.com/boostorg/nowide/workflows/CI%20Tests/badge.svg?branch=develop) ![](https://github.com/boostorg/nowide/workflows/POSIX/badge.svg?branch=develop) | [![codecov](https://codecov.io/gh/boostorg/nowide/branch/develop/graph/badge.svg)](https://codecov.io/gh/boostorg/nowide/branch/develop) | [![Deps](https://img.shields.io/badge/deps-develop-brightgreen.svg)](https://pdimov.github.io/boostdep-report/develop/nowide.html) | [![Documentation](https://img.shields.io/badge/docs-develop-brightgreen.svg)](https://www.boost.org/doc/libs/develop/libs/nowide/index.html) | [![Enter the Matrix](https://img.shields.io/badge/matrix-develop-brightgreen.svg)](https://www.boost.org/development/tests/develop/developer/nowide.html) Quality checks: [![Coverity Scan Build Status](https://scan.coverity.com/projects/20464/badge.svg)](https://scan.coverity.com/projects/boostorg-nowide) @@ -15,7 +15,7 @@ The library provides an implementation of standard C and C++ library functions, ### License -Distributed under the [Boost Software License, Version 1.0](http://www.boost.org/LICENSE_1_0.txt). +Distributed under the [Boost Software License, Version 1.0](https://www.boost.org/LICENSE_1_0.txt). ### Properties @@ -29,9 +29,10 @@ This is different to the version available prior to the inclusion in Boost. ### Requirements (All versions) -* C++11 (or higher) compatible compiler +* **C++11** (or higher) compatible compiler * MSVC 2015 and up work * libstdc++ < 5 is unsupported as it is silently lacking C++11 features + * When building with B2 pass e.g. `cxxstd=11` if your compiler defaults to C++03 ### Requirements (Boost version) @@ -104,5 +105,5 @@ Boost.Nowide integrates with Boost.Filesystem: * [Ask questions](http://stackoverflow.com/questions/ask?tags=c%2B%2B,boost,boost-nowide) * [Report bugs](https://github.com/boostorg/nowide/issues): Be sure to mention Boost version, platform and compiler you're using. A small compilable code sample to reproduce the problem is always good as well. -* Submit your patches as pull requests against **develop** branch. Note that by submitting patches you agree to license your modifications under the [Boost Software License, Version 1.0](http://www.boost.org/LICENSE_1_0.txt). -* Discussions about the library are held on the [Boost developers mailing list](http://www.boost.org/community/groups.html#main). Be sure to read the [discussion policy](http://www.boost.org/community/policy.html) before posting and add the `[nowide]` tag at the beginning of the subject line. +* Submit your patches as pull requests against **develop** branch. Note that by submitting patches you agree to license your modifications under the [Boost Software License, Version 1.0](https://www.boost.org/LICENSE_1_0.txt). +* Discussions about the library are held on the [Boost developers mailing list](https://www.boost.org/community/groups.html#main). Be sure to read the [discussion policy](https://www.boost.org/community/policy.html) before posting and add the `[nowide]` tag at the beginning of the subject line. diff --git a/build/Jamfile.v2 b/build/Jamfile.v2 index e1822798..8ea6ae74 100644 --- a/build/Jamfile.v2 +++ b/build/Jamfile.v2 @@ -1,12 +1,11 @@ -# Boost Nowide Library Build Jamfile - # Copyright (c) 2002, 2006 Beman Dawes # Copyright (c) 2012 Artyom Beilis (Tonkikh) # Copyright (c) 2020-2021 Alexander Grund # # Distributed under the Boost Software License, Version 1.0. -# (See accompanying file LICENSE or www.boost.org/LICENSE_1_0.txt) -# See library home page at http://www.boost.org/libs/nowide +# https://www.boost.org/LICENSE_1_0.txt +# +# See library home page at https://www.boost.org/libs/nowide import ../../config/checks/config : requires ; import configure ; @@ -22,12 +21,20 @@ project boost/nowide : source-location ../src : requirements $(requirements) [ requires + cxx11_auto_declarations + cxx11_char16_t + cxx11_char32_t + cxx11_constexpr + cxx11_decltype cxx11_defaulted_functions cxx11_noexcept + cxx11_nullptr + cxx11_override cxx11_rvalue_references cxx11_static_assert + cxx11_template_aliases + cxx11_variadic_templates ] - [ check-target-builds ../config//cxx11_moveable_fstreams "std::fstream is moveable and swappable" : : no ] [ check-target-builds ../config//lfs_support "Has Large File Support" : : BOOST_NOWIDE_NO_LFS ] no:BOOST_NOWIDE_NO_LFS [ check-target-builds ../config//attribute_init_priority "Has attribute init_priority" : BOOST_NOWIDE_HAS_INIT_PRIORITY ] : usage-requirements $(requirements) @@ -37,6 +44,7 @@ local SOURCES = console_buffer cstdio cstdlib filebuf iostream stat ; lib boost_nowide : $(SOURCES).cpp + : ../src ; boost-install boost_nowide ; diff --git a/config/check_attribute_init_priority.cpp b/config/check_attribute_init_priority.cpp index 7525fe08..fab03e24 100644 --- a/config/check_attribute_init_priority.cpp +++ b/config/check_attribute_init_priority.cpp @@ -1,8 +1,7 @@ -// Copyright (c) 2021 Alexander Grund -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at http://www.boost.org/LICENSE.txt) +// Copyright (c) 2021 Alexander Grund // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt class Foo {}; diff --git a/config/check_lfs_support.cpp b/config/check_lfs_support.cpp index 095c614f..791787f7 100644 --- a/config/check_lfs_support.cpp +++ b/config/check_lfs_support.cpp @@ -1,9 +1,8 @@ // -// Copyright (c) 2020 Alexander Grund -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at http://www.boost.org/LICENSE.txt) +// Copyright (c) 2020 Alexander Grund // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt #define _LARGEFILE_SOURCE #ifndef _FILE_OFFSET_BITS diff --git a/config/check_movable_fstreams.cpp b/config/check_movable_fstreams.cpp index 4970d9d7..ed6be385 100644 --- a/config/check_movable_fstreams.cpp +++ b/config/check_movable_fstreams.cpp @@ -1,9 +1,8 @@ // -// Copyright (c) 2020 Alexander Grund -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at http://www.boost.org/LICENSE.txt) +// Copyright (c) 2020 Alexander Grund // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt #include #include diff --git a/doc/Jamfile.v2 b/doc/Jamfile.v2 index b6f0ba6b..e03d6af3 100644 --- a/doc/Jamfile.v2 +++ b/doc/Jamfile.v2 @@ -1,7 +1,7 @@ # Copyright (C) 2019-2019 Alexander Grund +# # Distributed under the Boost Software License, Version 1.0 -# (see accompanying file LICENSE or a copy at -# http://www.boost.org/LICENSE_1_0.txt) +# https://www.boost.org/LICENSE_1_0.txt using doxygen ; import doxygen ; diff --git a/doc/LICENSE_1_0.txt b/doc/LICENSE_1_0.txt deleted file mode 100644 index 36b7cd93..00000000 --- a/doc/LICENSE_1_0.txt +++ /dev/null @@ -1,23 +0,0 @@ -Boost Software License - Version 1.0 - August 17th, 2003 - -Permission is hereby granted, free of charge, to any person or organization -obtaining a copy of the software and accompanying documentation covered by -this license (the "Software") to use, reproduce, display, distribute, -execute, and transmit the Software, and to prepare derivative works of the -Software, and to permit third-parties to whom the Software is furnished to -do so, all subject to the following: - -The copyright notices in the Software and this entire statement, including -the above license grant, this restriction and the following disclaimer, -must be included in all copies of the Software, in whole or in part, and -all derivative works of the Software, unless such copies or derivative -works are solely in the form of machine-executable object code generated by -a source language processor. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT -SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE -FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/doc/changelog.dox b/doc/changelog.dox index 1b5627ea..56cc560e 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -1,23 +1,42 @@ // -// Copyright (c) 2019-2021 Alexander Grund -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2019-2024 Alexander Grund // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt /*! \page changelog_page Changelog \section changelog Changelog -\subsection changelog_next Next release +\subsection changelog_11_3_1 Nowide 11.3.1 (Boost 1.88) +- Fix redefinition of `_CRT_SECURE_NO_WARNINGS` +- Make `getenv` thread-safe -- Fix possible double-free when setting a custom buffer (`setbuf`) after filebuf already allocated an internal buffer -- Handle some warnings (mostly on MSVC) +\subsection changelog_11_3_0 Nowide 11.3.0 (Boost 1.82) +- Add `convert_string` overload accepting a string +- Add `quoted` to output (quoted) paths (std::filesystem or boost::filesystem) + +\subsection changelog_11_2_0 Nowide 11.2.0 (Boost 1.80) +- `filebuf`: Major performance improvement for Bulk I/O +- `filebuf`: Fix wrong return value of `sync` when `fflush` failed +- `filebuf`: Fix possible undefined behavior in a corner case when nothing was actually written but buffer is in "write" mode +- `filebuf`: Limit putback of characters (i.e. `pbackfail`) only allowing putback of buffered characters (may be only 1 character) +- Add missing define `NOWIDE_USE_WCHAR_OVERLOADS` (standalone only) -\subsection changelog_11_1_3 Nowide 11.1.3 +\subsection changelog_11_1_4 Nowide 11.1.4 (Boost 1.79) +- Fix possible redefinition of `_LARGEFILE_SOURCE` +- Fix missing include when `BOOST_USE_WINDOWS_H` and `WIN32_LEAN_AND_MEAN` are defined. +- Fix compile failures on recent MinGW-w64 compilers +- Add sanity checking of the buffer size passed to the (possibly) 64 bit `stat` function +- Known issues: Read performance for text files is possibly worse than the `std` streams. Binary files and writing is unaffected. +- Fix possible use-after-free when reusing a closed filebuf + + +\subsection changelog_11_1_3 Nowide 11.1.3 (Boost 1.78) - Fix missing config file in release +- Fix possible double-free when setting a custom buffer (`setbuf`) after filebuf already allocated an internal buffer +- Handle some warnings (mostly on MSVC) - Known issues: Read performance for text files is degraded. Binary files and writing is unaffected. \subsection changelog_11_1_2 Nowide 11.1.2 (Boost 1.76) diff --git a/doc/gendoc.sh b/doc/gendoc.sh index e57dbc0e..cdf52a0c 100755 --- a/doc/gendoc.sh +++ b/doc/gendoc.sh @@ -1,11 +1,9 @@ #!/bin/bash + +# Copyright (c) 2009-2011 Artyom Beilis (Tonkikh) # -# Copyright (c) 2009-2011 Artyom Beilis (Tonkikh) -# -# Distributed under the Boost Software License, Version 1.0. (See -# accompanying file LICENSE or copy at -# http://www.boost.org/LICENSE_1_0.txt) -# +# Distributed under the Boost Software License, Version 1.0. +# https://www.boost.org/LICENSE_1_0.txt set -euo pipefail diff --git a/doc/main.dox b/doc/main.dox index 2d34c5f2..0aeeef0e 100644 --- a/doc/main.dox +++ b/doc/main.dox @@ -1,11 +1,9 @@ // -// Copyright (c) 2009-2011 Artyom Beilis (Tonkikh) -// Copyright (c) 2019-2020 Alexander Grund -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2009-2011 Artyom Beilis (Tonkikh) +// Copyright (c) 2019-2020 Alexander Grund // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt /*! @@ -24,6 +22,7 @@ Table of Contents: - \ref alternative - \ref main_reading - \ref using + - \ref building - \ref using_standard - \ref using_custom - \ref using_integration @@ -139,10 +138,15 @@ Since April 2018 there is a (Beta) function available in Windows 10 to use UTF-8 Both methods do work but have a major drawback: They are not fully reliable for the app developer. The code page via manifest method falls back to a legacy code page when an older Windows version than 1903 is used. -Hence it is only usable if the targetted system is Windows 10 after May 2019. +Hence it is only usable if the targeted system is Windows 10 after May 2019. The second method relies on user interaction prior to starting the program. Obviously this is not reliable when expecting only UTF-8 in the code. +Also since Windows 10 1803 (i.e. since April 2018) it is possible to programmatically set the current code page to UTF-8 with e.g. `setlocale(LC_ALL, ".UTF8");`. +This makes many functions accept or produce UTF-8 encoded strings which is especially useful for `std::filesystem::path` and its `string()` function. +See the documentation for details. +While this works for most functions, it doesn't work for e.g. the program arguments (`argv` and `env` parameters of `main`). + Hence under some circumstances (and hopefully always somewhen in the future) this library will not be required and even Windows I/O can be used with UTF-8 encoded text. \subsection main_reading Further Reading @@ -151,6 +155,18 @@ Hence under some circumstances (and hopefully always somewhen in the future) thi - Windows console I/O approaches \section using Using The Library +\subsection building Building the library + +Boost.Nowide is usually build as part of Boost via `b2`. +It requires C++11 features and if any are missing the library will **not** be build. + +If that happens unexpectatly watch the configuration check output for anything like `cxx11_constexpr : no`. +This means your compiler doesn't use C++11 (or higher), e.g. because it defaults to C++03. +You can pass `cxxstd=11` to `b2` to build in C++11 mode. + +Experimental support for building with the Boost CMake build system is also available. +For that run e.g. `cmake -DBOOST_INCLUDE_LIBRARIES=nowide `. + \subsection using_standard Standard Features As a developer you are expected to use \c boost::nowide functions instead of the functions available in the @@ -227,7 +243,7 @@ and temporarily replaces the original \c argv (and optionally \c env) with point UTF-8 strings for the lifetime of the instance. - \c boost::nowide::ifstream converts the passed filename (which is now valid UTF-8) to UTF-16 and calls the Windows Wide API to open the file stream which can then be used as usual. -- Similarily \c boost::nowide::cerr and \c boost::nowide::cout use an underlying stream buffer +- Similarly \c boost::nowide::cerr and \c boost::nowide::cout use an underlying stream buffer that converts the UTF-8 string to UTF-16 and use another Wide API function to write it to console. \subsection using_custom Custom API @@ -255,7 +271,7 @@ The example above could be rewritten as: \code boost::nowide::basic_stackstring wexisting_file(existing_file), wnew_file(new_file); -CopyFileW(wexisting_file.c_str(),wnew_file.c_str(),TRUE); +CopyFileW(wexisting_file.c_str(), wnew_file.c_str(), TRUE); \endcode \note There are a few convenience typedefs: \c stackstring and \c wstackstring using @@ -273,15 +289,44 @@ before including any of the Boost.Nowide headers \subsection using_integration Integration with Boost.Filesystem Boost.Filesystem supports selection of narrow encoding. -Unfortunatelly the default narrow encoding on Windows isn't UTF-8. +Unfortunately the default narrow encoding on Windows isn't UTF-8. But you can enable UTF-8 as default encoding on Boost.Filesystem by calling `boost::nowide::nowide_filesystem()` in the beginning of your program which -imbues a locale with a UTF-8 conversion facet to convert between \c char \c wchar_t. +imbues a locale with a UTF-8 conversion facet to convert between \c char and \c wchar_t. This interprets all narrow strings passed to and from \c boost::filesystem::path as UTF-8 when converting them to wide strings (as required for internal storage). On POSIX this has usually no effect, as no conversion is done due to narrow strings being used as the storage format. +For `std::filesystem::path` available since C++17 there is no way to imbue a locale. +However the `u8string()` member function can be used to obtain an UTF-8 encoded string from a `path`. +And to obtain a `path` from an UTF-8 encoded string you may use `std::filesystem::u8path` +or since C++20 one of the `path` constructors taking a `char8_t`-type input. + +To read/write `std::filesystem::path` instances from/to streams you'd usually use e.g. `os << path`. +However that will effectively be run as `os << std::quoted(path.string())` which means a possible conversion +to a narrow string which may not be UTF-8 encoded. +For that \c quoted can be used: + +\code +#include +#include +#include + +std::string write(const std::filesystem::path& path) +{ + std::ostringstream s; + s << boost::nowide::quoted(path); + return s.str(); +} + +std::experimental::path read(std::istream& is) +{ + std::filesystem::path path; + is >> boost::nowide::quoted(path); + return path; +} +\endcode \section technical Technical Details \subsection technical_imple Windows vs POSIX diff --git a/include/boost/nowide/args.hpp b/include/boost/nowide/args.hpp index e11b72cf..6c382dce 100644 --- a/include/boost/nowide/args.hpp +++ b/include/boost/nowide/args.hpp @@ -1,10 +1,9 @@ // -// Copyright (c) 2012 Artyom Beilis (Tonkikh) -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2012 Artyom Beilis (Tonkikh) // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + #ifndef BOOST_NOWIDE_ARGS_HPP_INCLUDED #define BOOST_NOWIDE_ARGS_HPP_INCLUDED @@ -113,7 +112,7 @@ namespace nowide { } operator bool() const { - return p != NULL; + return p != nullptr; } const wchar_t* operator[](size_t i) const { diff --git a/include/boost/nowide/config.hpp b/include/boost/nowide/config.hpp index be7dc611..1ba7c5f5 100644 --- a/include/boost/nowide/config.hpp +++ b/include/boost/nowide/config.hpp @@ -1,11 +1,10 @@ // -// Copyright (c) 2012 Artyom Beilis (Tonkikh) -// Copyright (c) 2019 - 2020 Alexander Grund -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2012 Artyom Beilis (Tonkikh) +// Copyright (c) 2019 - 2022 Alexander Grund // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + #ifndef BOOST_NOWIDE_CONFIG_HPP_INCLUDED #define BOOST_NOWIDE_CONFIG_HPP_INCLUDED @@ -27,9 +26,7 @@ #define BOOST_NOWIDE_DECL #endif // BOOST_NOWIDE_DYN_LINK -// // Automatically link to the correct build variant where possible. -// #if !defined(BOOST_ALL_NO_LIB) && !defined(BOOST_NOWIDE_NO_LIB) && !defined(BOOST_NOWIDE_SOURCE) // // Set the name of our library, this will get undef'ed by auto_link.hpp @@ -51,35 +48,39 @@ //! @endcond /// @def BOOST_NOWIDE_USE_WCHAR_OVERLOADS -/// @brief Whether to use the wchar_t* overloads in fstream/filebuf -/// Enabled on Windows and Cygwin as the latter may use wchar_t in filesystem::path -#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) +/// @brief Whether to use the wchar_t* overloads in fstream-classes. +/// +/// Enabled by default on Windows and Cygwin as the latter may use wchar_t in filesystem::path. +#ifndef BOOST_NOWIDE_USE_WCHAR_OVERLOADS +#if defined(BOOST_WINDOWS) || defined(__CYGWIN__) || defined(BOOST_NOWIDE_DOXYGEN) #define BOOST_NOWIDE_USE_WCHAR_OVERLOADS 1 #else #define BOOST_NOWIDE_USE_WCHAR_OVERLOADS 0 #endif +#endif /// @def BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT -/// @brief Define to 1 to use internal class from filebuf.hpp +/// @brief Define to 1 to use the class from that is used on Windows. /// -/// - On Non-Windows platforms: Define to 1 to use the same class from header -/// that is used on Windows. /// - On Windows: No effect, always overwritten to 1 +/// - Others (including Cygwin): Defaults to the value of #BOOST_NOWIDE_USE_WCHAR_OVERLOADS if not set. +/// +/// When set to 0 boost::nowide::basic_filebuf will be an alias for std::basic_filebuf. /// /// Affects boost::nowide::basic_filebuf, /// boost::nowide::basic_ofstream, boost::nowide::basic_ifstream, boost::nowide::basic_fstream -#if defined(BOOST_WINDOWS) || BOOST_NOWIDE_USE_WCHAR_OVERLOADS +#if defined(BOOST_WINDOWS) || defined(BOOST_NOWIDE_DOXYGEN) #ifdef BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT #undef BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT #endif #define BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT 1 #elif !defined(BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT) -#define BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT 0 +#define BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT BOOST_NOWIDE_USE_WCHAR_OVERLOADS #endif //! @cond Doxygen_Suppress -#if BOOST_VERSION < 106500 && defined(BOOST_GCC) && __GNUC__ >= 7 +#if BOOST_VERSION < 106500 && defined(__GNUC__) && __GNUC__ >= 7 #define BOOST_NOWIDE_FALLTHROUGH __attribute__((fallthrough)) #else #define BOOST_NOWIDE_FALLTHROUGH BOOST_FALLTHROUGH @@ -115,4 +116,4 @@ namespace boost { namespace nowide {} } // namespace boost -#endif // boost/nowide/config.hpp +#endif diff --git a/include/boost/nowide/convert.hpp b/include/boost/nowide/convert.hpp index 5e913d8a..10bc2f1f 100644 --- a/include/boost/nowide/convert.hpp +++ b/include/boost/nowide/convert.hpp @@ -1,11 +1,10 @@ // -// Copyright (c) 2012 Artyom Beilis (Tonkikh) -// Copyright (c) 2020 Alexander Grund -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2012 Artyom Beilis (Tonkikh) +// Copyright (c) 2020 Alexander Grund // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + #ifndef BOOST_NOWIDE_CONVERT_HPP_INCLUDED #define BOOST_NOWIDE_CONVERT_HPP_INCLUDED diff --git a/include/boost/nowide/cstdio.hpp b/include/boost/nowide/cstdio.hpp index f3b4b5db..4cdad947 100644 --- a/include/boost/nowide/cstdio.hpp +++ b/include/boost/nowide/cstdio.hpp @@ -1,11 +1,10 @@ // -// Copyright (c) 2012 Artyom Beilis (Tonkikh) -// Copyright (c) 2020 Alexander Grund -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2012 Artyom Beilis (Tonkikh) +// Copyright (c) 2020 Alexander Grund // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + #ifndef BOOST_NOWIDE_CSTDIO_HPP_INCLUDED #define BOOST_NOWIDE_CSTDIO_HPP_INCLUDED diff --git a/include/boost/nowide/cstdlib.hpp b/include/boost/nowide/cstdlib.hpp index 1f89e729..cfdac7cf 100644 --- a/include/boost/nowide/cstdlib.hpp +++ b/include/boost/nowide/cstdlib.hpp @@ -1,10 +1,9 @@ // -// Copyright (c) 2012 Artyom Beilis (Tonkikh) -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2012 Artyom Beilis (Tonkikh) // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + #ifndef BOOST_NOWIDE_CSTDLIB_HPP_INCLUDED #define BOOST_NOWIDE_CSTDLIB_HPP_INCLUDED @@ -22,7 +21,12 @@ namespace nowide { /// /// \brief UTF-8 aware getenv. Returns 0 if the variable is not set. /// - /// This function is not thread safe or reenterable as defined by the standard library + /// The string pointed to shall not be modified by the program. + /// This function is thread-safe as long as no other thread modifies the host environment. + /// However subsequent calls to this function might overwrite the string pointed to. + /// + /// Warning: The returned pointer might only be valid for as long as the calling thread is alive. + /// So avoid passing it across thread boundaries. /// BOOST_NOWIDE_DECL char* getenv(const char* key); diff --git a/include/boost/nowide/detail/convert.hpp b/include/boost/nowide/detail/convert.hpp index 5fcf3a1a..39593fa7 100644 --- a/include/boost/nowide/detail/convert.hpp +++ b/include/boost/nowide/detail/convert.hpp @@ -1,10 +1,9 @@ // -// Copyright (c) 2020 Alexander Grund -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2020 Alexander Grund // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + #ifndef BOOST_NOWIDE_DETAIL_CONVERT_HPP_INCLUDED #define BOOST_NOWIDE_DETAIL_CONVERT_HPP_INCLUDED diff --git a/include/boost/nowide/detail/is_path.hpp b/include/boost/nowide/detail/is_path.hpp index aee11b43..4419a951 100644 --- a/include/boost/nowide/detail/is_path.hpp +++ b/include/boost/nowide/detail/is_path.hpp @@ -1,10 +1,9 @@ // -// Copyright (c) 2020 Alexander Grund -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2020 Alexander Grund // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + #ifndef BOOST_NOWIDE_DETAIL_IS_PATH_HPP_INCLUDED #define BOOST_NOWIDE_DETAIL_IS_PATH_HPP_INCLUDED @@ -28,7 +27,7 @@ namespace nowide { static constexpr bool value = decltype(test(0))::value; }; - /// SFINAE trait which has a member "type = Result" if the Path is a *\::filesystem::path + /// SFINAE trait/alias which resolves to Result if the Path is a *\::filesystem::path template using enable_if_path_t = typename std::enable_if::value, Result>::type; diff --git a/include/boost/nowide/detail/is_string_container.hpp b/include/boost/nowide/detail/is_string_container.hpp index 1b54a608..ffaa94b4 100644 --- a/include/boost/nowide/detail/is_string_container.hpp +++ b/include/boost/nowide/detail/is_string_container.hpp @@ -1,10 +1,9 @@ // -// Copyright (c) 2020 Alexander Grund -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2020 Alexander Grund // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + #ifndef BOOST_NOWIDE_DETAIL_IS_STRING_CONTAINER_HPP_INCLUDED #define BOOST_NOWIDE_DETAIL_IS_STRING_CONTAINER_HPP_INCLUDED diff --git a/include/boost/nowide/detail/utf.hpp b/include/boost/nowide/detail/utf.hpp index 09302817..11b723fa 100644 --- a/include/boost/nowide/detail/utf.hpp +++ b/include/boost/nowide/detail/utf.hpp @@ -1,10 +1,9 @@ // -// Copyright (c) 2020 Alexander Grund -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2020 Alexander Grund // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + #ifndef BOOST_NOWIDE_DETAIL_UTF_HPP_INCLUDED #define BOOST_NOWIDE_DETAIL_UTF_HPP_INCLUDED diff --git a/include/boost/nowide/filebuf.hpp b/include/boost/nowide/filebuf.hpp index 44c7a513..5e124243 100644 --- a/include/boost/nowide/filebuf.hpp +++ b/include/boost/nowide/filebuf.hpp @@ -1,17 +1,17 @@ // -// Copyright (c) 2012 Artyom Beilis (Tonkikh) -// Copyright (c) 2019-2020 Alexander Grund -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2012 Artyom Beilis (Tonkikh) +// Copyright (c) 2019-2020 Alexander Grund // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + #ifndef BOOST_NOWIDE_FILEBUF_HPP_INCLUDED #define BOOST_NOWIDE_FILEBUF_HPP_INCLUDED #include #if BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT #include +#include #include #include #include @@ -38,9 +38,10 @@ namespace nowide { using std::filebuf; #else // Windows /// - /// \brief This forward declaration defines the basic_filebuf type. + /// \brief This forward declaration defines the basic_filebuf type + /// which is used when #BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT is set, e.g. on Windows. /// - /// it is implemented and specialized for CharType = char, it + /// It is implemented and specialized for CharType = char, it /// implements std::filebuf over standard C I/O /// template> @@ -48,8 +49,9 @@ namespace nowide { /// /// \brief This is the implementation of std::filebuf + /// which is used when #BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT is set, e.g. on Windows. /// - /// it is implemented and specialized for CharType = char, it + /// It is implemented and specialized for CharType = char, it /// implements std::filebuf over standard C I/O /// template<> @@ -66,11 +68,11 @@ namespace nowide { /// Creates new filebuf /// basic_filebuf() : - buffer_size_(BUFSIZ), buffer_(0), file_(0), owns_buffer_(false), last_char_(), - mode_(std::ios_base::openmode(0)) + file_(nullptr), buffer_(nullptr), buffer_size_(BUFSIZ), owns_buffer_(false), unbuffered_read_(false), + last_char_(), mode_(std::ios_base::openmode(0)) { - setg(0, 0, 0); - setp(0, 0); + setg(nullptr, nullptr, nullptr); + setp(nullptr, nullptr); } #ifdef BOOST_MSVC #pragma warning(pop) @@ -91,10 +93,11 @@ namespace nowide { { std::basic_streambuf::swap(rhs); using std::swap; - swap(buffer_size_, rhs.buffer_size_); - swap(buffer_, rhs.buffer_); swap(file_, rhs.file_); + swap(buffer_, rhs.buffer_); + swap(buffer_size_, rhs.buffer_size_); swap(owns_buffer_, rhs.owns_buffer_); + swap(unbuffered_read_, rhs.unbuffered_read_); swap(last_char_[0], rhs.last_char_[0]); swap(mode_, rhs.mode_); @@ -138,70 +141,59 @@ namespace nowide { basic_filebuf* open(const wchar_t* s, std::ios_base::openmode mode) { if(is_open()) - return NULL; + return nullptr; validate_cvt(this->getloc()); const bool ate = (mode & std::ios_base::ate) != 0; if(ate) mode &= ~std::ios_base::ate; const wchar_t* smode = get_mode(mode); if(!smode) - return 0; + return nullptr; file_ = detail::wfopen(s, smode); if(!file_) - return 0; + return nullptr; if(ate && detail::fseek(file_, 0, SEEK_END) != 0) { close(); - return 0; + return nullptr; } mode_ = mode; + set_unbuffered_read(); return this; } + template + detail::enable_if_path_t open(const Path& file_name, std::ios_base::openmode mode) + { + return open(file_name.c_str(), mode); + } /// /// Same as std::filebuf::close() /// basic_filebuf* close() { if(!is_open()) - return NULL; + return nullptr; bool res = sync() == 0; if(std::fclose(file_) != 0) res = false; - file_ = NULL; + file_ = nullptr; mode_ = std::ios_base::openmode(0); if(owns_buffer_) { delete[] buffer_; - buffer_ = NULL; + buffer_ = nullptr; owns_buffer_ = false; } - setg(0, 0, 0); - setp(0, 0); - return res ? this : NULL; + setg(nullptr, nullptr, nullptr); + setp(nullptr, nullptr); + return res ? this : nullptr; } /// /// Same as std::filebuf::is_open() /// bool is_open() const { - return file_ != NULL; - } - - private: - void make_buffer() - { - if(buffer_) - return; - if(buffer_size_ > 0) - { - buffer_ = new char[buffer_size_]; - owns_buffer_ = true; - } - } - void validate_cvt(const std::locale& loc) - { - if(!std::use_facet>(loc).always_noconv()) - throw std::runtime_error("Converting codecvts are not supported"); + return file_ != nullptr; } protected: @@ -210,8 +202,8 @@ namespace nowide { assert(n >= 0); // Maximum compatibility: Discard all local buffers and use user-provided values // Users should call sync() before or better use it before any IO is done or any file is opened - setg(NULL, NULL, NULL); - setp(NULL, NULL); + setg(nullptr, nullptr, nullptr); + setp(nullptr, nullptr); if(owns_buffer_) { delete[] buffer_; @@ -219,9 +211,29 @@ namespace nowide { } buffer_ = s; buffer_size_ = (n >= 0) ? static_cast(n) : 0; + set_unbuffered_read(); return this; } + int sync() override + { + if(!file_) + return 0; + bool result; + if(pptr()) + { + // Only flush if anything was written, otherwise behavior of fflush is undefined. I.e.: + // - Buffered mode: pptr was set to buffer_ and advanced + // - Unbuffered mode: pptr set to last_char_ + const bool has_prev_write = pptr() != buffer_; + result = overflow() != EOF; + if(has_prev_write && std::fflush(file_) != 0) + result = false; + } else + result = stop_reading(); + return result ? 0 : -1; + } + int overflow(int c = EOF) override { if(!(mode_ & (std::ios_base::out | std::ios_base::app))) @@ -235,6 +247,7 @@ namespace nowide { { if(std::fwrite(pbase(), 1, n, file_) != n) return EOF; + assert(buffer_); setp(buffer_, buffer_ + buffer_size_); if(c != EOF) { @@ -261,33 +274,37 @@ namespace nowide { return Traits::not_eof(c); } - int sync() override + std::streamsize xsputn(const char* s, std::streamsize n) override { - if(!file_) + // Only optimize when writing more than a buffer worth of data + if(n <= static_cast(buffer_size_)) + return std::basic_streambuf::xsputn(s, n); + if(!(mode_ & (std::ios_base::out | std::ios_base::app)) || !stop_reading()) return 0; - bool result; - if(pptr()) + + assert(n >= 0); + // First empty the remaining put area, if any + const char* const base = pbase(); + const size_t num_buffered = pptr() - base; + if(num_buffered != 0) { - result = overflow() != EOF; - // Only flush if anything was written, otherwise behavior of fflush is undefined - if(std::fflush(file_) != 0) - return result = false; - } else - result = stop_reading(); - return result ? 0 : -1; + const auto num_written = std::fwrite(base, 1, num_buffered, file_); + setp(const_cast(base + num_written), epptr()); // i.e. pbump(num_written) + if(num_written != num_buffered) + return 0; // Error writing buffered chars + } + // Then write directly to file + const auto num_written = std::fwrite(s, 1, static_cast(n), file_); + if(num_written > 0u && base != last_char_) + setp(last_char_, last_char_); // Mark as "written" if not done yet + return num_written; } int underflow() override { - if(!(mode_ & std::ios_base::in)) - return EOF; - if(!stop_writing()) + if(!(mode_ & std::ios_base::in) || !stop_writing()) return EOF; - // In text mode we cannot use a buffer size of more than 1 (i.e. single char only) - // This is due to the need to seek back in case of a sync to "put back" unread chars. - // However determining the number of chars to seek back is impossible in case there are newlines - // as we cannot know if those were converted. - if(buffer_size_ == 0 || !(mode_ & std::ios_base::binary)) + if(unbuffered_read_) { const int c = std::fgetc(file_); if(c == EOF) @@ -305,27 +322,50 @@ namespace nowide { return Traits::to_int_type(*gptr()); } + std::streamsize xsgetn(char* s, std::streamsize n) override + { + // Only optimize when reading more than a buffer worth of data + if(n <= static_cast(unbuffered_read_ ? 1u : buffer_size_)) + return std::basic_streambuf::xsgetn(s, n); + if(!(mode_ & std::ios_base::in) || !stop_writing()) + return 0; + assert(n >= 0); + std::streamsize num_copied = 0; + // First empty the remaining get area, if any + const auto num_buffered = egptr() - gptr(); + if(num_buffered != 0) + { + const auto num_read = num_buffered > n ? n : num_buffered; + traits_type::copy(s, gptr(), static_cast(num_read)); + s += num_read; + n -= num_read; + num_copied = num_read; + setg(eback(), gptr() + num_read, egptr()); // i.e. gbump(num_read) + } + // Then read directly from file (loop as number of bytes read may be less than requested) + while(n > 0) + { + const auto num_read = std::fread(s, 1, static_cast(n), file_); + if(num_read == 0) // EOF or error + break; + s += num_read; + n -= num_read; + num_copied += num_read; + } + return num_copied; + } + int pbackfail(int c = EOF) override { - if(!(mode_ & std::ios_base::in)) - return EOF; - if(!stop_writing()) - return EOF; + // For simplicity we only allow putting back into our read buffer + // So putting back more chars than we have read from the buffer will fail if(gptr() > eback()) gbump(-1); - else if(seekoff(-1, std::ios_base::cur) != std::streampos(std::streamoff(-1))) - { - if(underflow() == EOF) - return EOF; - } else + else return EOF; - // Case 1: Caller just wanted space for 1 char - if(c == EOF) - return Traits::not_eof(c); - // Case 2: Caller wants to put back different char - // gptr now points to the (potentially newly read) previous char - if(*gptr() != c) + // Assign the new value if requested + if(c != EOF && *gptr() != Traits::to_char_type(c)) *gptr() = Traits::to_char_type(c); return Traits::not_eof(c); } @@ -366,14 +406,40 @@ namespace nowide { } private: + void make_buffer() + { + if(buffer_) + return; + if(buffer_size_ > 0) + { + buffer_ = new char[buffer_size_]; + owns_buffer_ = true; + } + } + + void set_unbuffered_read() + { + // In text mode we cannot use buffering as we are required to know the (file) position of each + // char in the get area and to seek back in case of a sync to "put back" unread chars. + // However std::fseek with non-zero offsets is unsupported for text files and the (file) offset + // to seek back is unknown anyway due to newlines which may got converted. + unbuffered_read_ = !(mode_ & std::ios_base::binary) || buffer_size_ == 0u; + } + + void validate_cvt(const std::locale& loc) + { + if(!std::use_facet>(loc).always_noconv()) + throw std::runtime_error("Converting codecvts are not supported"); + } + /// Stop reading adjusting the file pointer if necessary - /// Postcondition: gptr() == NULL + /// Postcondition: gptr() == nullptr bool stop_reading() { if(!gptr()) return true; const auto off = gptr() - egptr(); - setg(0, 0, 0); + setg(nullptr, nullptr, nullptr); if(!off) return true; #if defined(__clang__) @@ -381,7 +447,7 @@ namespace nowide { #pragma clang diagnostic ignored "-Wtautological-constant-out-of-range-compare" #endif // coverity[result_independent_of_operands] - if(off > std::numeric_limits::max()) + if(off < std::numeric_limits::min()) return false; #if defined(__clang__) #pragma clang diagnostic pop @@ -390,31 +456,20 @@ namespace nowide { } /// Stop writing. If any bytes are to be written, writes them to file - /// Postcondition: pptr() == NULL + /// Postcondition: pptr() == nullptr bool stop_writing() { if(pptr()) { const char* const base = pbase(); const size_t n = pptr() - base; - setp(0, 0); + setp(nullptr, nullptr); if(n && std::fwrite(base, 1, n, file_) != n) return false; } return true; } - void reset(FILE* f = 0) - { - sync(); - if(file_) - { - fclose(file_); - file_ = 0; - } - file_ = f; - } - static const wchar_t* get_mode(std::ios_base::openmode mode) { // @@ -459,13 +514,14 @@ namespace nowide { return L"a+b"; if(mode == (std::ios_base::binary | std::ios_base::in | std::ios_base::app)) return L"a+b"; - return 0; + return nullptr; } - size_t buffer_size_; - char* buffer_; FILE* file_; + char* buffer_; + size_t buffer_size_; bool owns_buffer_; + bool unbuffered_read_; // True to read char by char char last_char_[1]; std::ios::openmode mode_; }; diff --git a/include/boost/nowide/filesystem.hpp b/include/boost/nowide/filesystem.hpp index 68e398d7..4ed205ec 100644 --- a/include/boost/nowide/filesystem.hpp +++ b/include/boost/nowide/filesystem.hpp @@ -1,10 +1,9 @@ // -// Copyright (c) 2012 Artyom Beilis (Tonkikh) -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2012 Artyom Beilis (Tonkikh) // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + #ifndef BOOST_NOWIDE_INTEGRATION_FILESYSTEM_HPP_INCLUDED #define BOOST_NOWIDE_INTEGRATION_FILESYSTEM_HPP_INCLUDED @@ -14,7 +13,9 @@ namespace boost { namespace nowide { /// - /// Install utf8_codecvt facet into boost::filesystem::path such all char strings are interpreted as utf-8 strings + /// Install utf8_codecvt facet into boost::filesystem::path + /// such that all char strings are interpreted as UTF-8 strings + /// \return The previous imbued path locale. /// inline std::locale nowide_filesystem() { diff --git a/include/boost/nowide/fstream.hpp b/include/boost/nowide/fstream.hpp index ca946f31..bcb271b3 100644 --- a/include/boost/nowide/fstream.hpp +++ b/include/boost/nowide/fstream.hpp @@ -1,10 +1,9 @@ // -// Copyright (c) 2012 Artyom Beilis (Tonkikh) -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2012 Artyom Beilis (Tonkikh) // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + #ifndef BOOST_NOWIDE_FSTREAM_HPP_INCLUDED #define BOOST_NOWIDE_FSTREAM_HPP_INCLUDED @@ -68,6 +67,7 @@ namespace nowide { /// /// \brief Same as std::basic_ifstream but accepts UTF-8 strings under Windows /// + /// Affected by #BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT and #BOOST_NOWIDE_USE_WCHAR_OVERLOADS template> class basic_ifstream : public detail::fstream_impl { @@ -118,7 +118,7 @@ namespace nowide { /// /// \brief Same as std::basic_ofstream but accepts UTF-8 strings under Windows /// - + /// Affected by #BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT and #BOOST_NOWIDE_USE_WCHAR_OVERLOADS template> class basic_ofstream : public detail::fstream_impl { @@ -171,6 +171,7 @@ namespace nowide { /// /// \brief Same as std::basic_fstream but accepts UTF-8 strings under Windows /// + /// Affected by #BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT and #BOOST_NOWIDE_USE_WCHAR_OVERLOADS template> class basic_fstream : public detail::fstream_impl { diff --git a/include/boost/nowide/iostream.hpp b/include/boost/nowide/iostream.hpp index 3c09203c..ed7cb9e8 100644 --- a/include/boost/nowide/iostream.hpp +++ b/include/boost/nowide/iostream.hpp @@ -1,9 +1,8 @@ -// Copyright (c) 2012 Artyom Beilis (Tonkikh) -// Copyright (c) 2020-2021 Alexander Grund +// Copyright (c) 2012 Artyom Beilis (Tonkikh) +// Copyright (c) 2020-2021 Alexander Grund // -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt #ifndef BOOST_NOWIDE_IOSTREAM_HPP_INCLUDED #define BOOST_NOWIDE_IOSTREAM_HPP_INCLUDED @@ -41,7 +40,13 @@ namespace nowide { class BOOST_NOWIDE_DECL winconsole_ostream : public std::ostream { public: - winconsole_ostream(bool isBuffered, winconsole_ostream* tieStream); + enum class target_stream + { + output, + error, + log, + }; + winconsole_ostream(target_stream target, bool isBuffered, winconsole_ostream* tieStream); ~winconsole_ostream(); private: diff --git a/include/boost/nowide/quoted.hpp b/include/boost/nowide/quoted.hpp new file mode 100644 index 00000000..d940fde6 --- /dev/null +++ b/include/boost/nowide/quoted.hpp @@ -0,0 +1,109 @@ +// +// Copyright (c) 2023 Alexander Grund +// +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_NOWIDE_QUOTED_HPP_INCLUDED +#define BOOST_NOWIDE_QUOTED_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include + +#if defined(__cpp_lib_quoted_string_io) && __cpp_lib_quoted_string_io >= 201304 + +namespace boost { +namespace nowide { + /// \cond INTERNAL + namespace detail { + template + struct quoted; + template + using remove_cvref_t = typename std::remove_cv::type>::type; + + } // namespace detail + /// \endcond + + /// \brief Allows insertion and extraction of `filesystem::path` into/from streams. + /// + /// When used in an expression such as `out << quoted(path)`, where `out` is an output stream, + /// has the effect as-if `out << std::quoted(path.native())` was used. + /// + /// When used in an expression like `in >> quoted(path)`, where `in` is an input stream, + /// has the effect as-if `in >> std::quoted(path.native())` was used if that would be valid. + /// To that effect a temporary string is used, which on success is assigned to `path`. + /// + /// Will automatically convert between the streams `char_type` and `path::value_type` if necessary. + template +#ifdef BOOST_NOWIDE_DOXYGEN + unspecified_type +#else + detail::enable_if_path_t, detail::quoted> +#endif + quoted(Path& path) + { + return {path}; + } + + /// \cond INTERNAL + // Same but for const-refs and r-values + template + detail::enable_if_path_t, detail::quoted> quoted(const Path& path) + { + return {path}; + } + + namespace detail { + template::value>::type> + std::basic_string maybe_convert_string(const std::basic_string& s) + { + return utf::convert_string(s); + } + template + const std::basic_string& maybe_convert_string(const std::basic_string& s) + { + return s; + } + + template + using requires_non_const = + typename std::enable_if::type>::value>::type; + + template + struct quoted + { + Path value; + template + friend std::basic_ostream& operator<<(std::basic_ostream& out, const quoted& path) + { + return out << std::quoted(maybe_convert_string(path.value.native())); + } + + template> + friend std::basic_istream& operator>>(std::basic_istream& in, const quoted& path) + { + std::basic_string value; + using PlainPath = remove_cvref_t; + if(in >> std::quoted(value)) + path.value = PlainPath(maybe_convert_string(value)); + return in; + } + }; + + } // namespace detail + /// \endcond +} // namespace nowide +} // namespace boost + +#elif defined(BOOST_PRAGMA_MESSAGE) +BOOST_PRAGMA_MESSAGE("To use boost::nowide::quoted at least C++14 is required.") +#endif + +#endif diff --git a/include/boost/nowide/replacement.hpp b/include/boost/nowide/replacement.hpp index 4f116277..e44c26bd 100644 --- a/include/boost/nowide/replacement.hpp +++ b/include/boost/nowide/replacement.hpp @@ -1,10 +1,9 @@ // -// Copyright (c) 2018 Artyom Beilis (Tonkikh) -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2018 Artyom Beilis (Tonkikh) // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + #ifndef BOOST_NOWIDE_REPLACEMENT_HPP_INCLUDED #define BOOST_NOWIDE_REPLACEMENT_HPP_INCLUDED diff --git a/include/boost/nowide/stackstring.hpp b/include/boost/nowide/stackstring.hpp index 129de78d..3261ac57 100644 --- a/include/boost/nowide/stackstring.hpp +++ b/include/boost/nowide/stackstring.hpp @@ -1,10 +1,9 @@ // -// Copyright (c) 2012 Artyom Beilis (Tonkikh) -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2012 Artyom Beilis (Tonkikh) // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + #ifndef BOOST_NOWIDE_STACKSTRING_HPP_INCLUDED #define BOOST_NOWIDE_STACKSTRING_HPP_INCLUDED @@ -26,7 +25,7 @@ namespace nowide { /// Invalid UTF characters are replaced by the substitution character, see #BOOST_NOWIDE_REPLACEMENT_CHARACTER /// /// If a NULL pointer is passed to the constructor or convert method, NULL will be returned by c_str. - /// Similarily a default constructed stackstring will return NULL on calling c_str. + /// Similarly a default constructed stackstring will return NULL on calling c_str. /// template class basic_stackstring @@ -40,24 +39,24 @@ namespace nowide { using input_char = CharIn; /// Creates a NULL stackstring - basic_stackstring() : data_(NULL) + basic_stackstring() { buffer_[0] = 0; } /// Convert the NULL terminated string input and store in internal buffer /// If input is NULL, nothing will be stored - explicit basic_stackstring(const input_char* input) : data_(NULL) + explicit basic_stackstring(const input_char* input) { convert(input); } /// Convert the sequence [begin, end) and store in internal buffer /// If begin is NULL, nothing will be stored - basic_stackstring(const input_char* begin, const input_char* end) : data_(NULL) + basic_stackstring(const input_char* begin, const input_char* end) { convert(begin, end); } /// Copy construct from other - basic_stackstring(const basic_stackstring& other) : data_(NULL) + basic_stackstring(const basic_stackstring& other) { *this = other; } @@ -74,7 +73,7 @@ namespace nowide { data_ = new output_char[len + 1]; else { - data_ = NULL; + data_ = nullptr; return *this; } std::memcpy(data_, other.data_, sizeof(output_char) * (len + 1)); @@ -138,7 +137,7 @@ namespace nowide { { if(!uses_stack_memory()) delete[] data_; - data_ = NULL; + data_ = nullptr; } /// Swap lhs with rhs friend void swap(basic_stackstring& lhs, basic_stackstring& rhs) @@ -186,7 +185,7 @@ namespace nowide { private: output_char buffer_[buffer_size]; - output_char* data_; + output_char* data_ = nullptr; }; // basic_stackstring /// diff --git a/include/boost/nowide/stat.hpp b/include/boost/nowide/stat.hpp index 4a55401d..2c269e61 100644 --- a/include/boost/nowide/stat.hpp +++ b/include/boost/nowide/stat.hpp @@ -1,9 +1,9 @@ // -// Copyright (c) 2020 Alexander Grund -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2020 Alexander Grund // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + #ifndef BOOST_NOWIDE_STAT_HPP_INCLUDED #define BOOST_NOWIDE_STAT_HPP_INCLUDED @@ -29,8 +29,8 @@ namespace nowide { using ::stat; #else /// \brief Typedef for the file info structure. - /// Able to hold 64 bit filesize and timestamps on Windows and usually also on other 64 Bit systems - /// This allows to write portable code with option LFS support + /// Able to hold 64 bit file size and timestamps on Windows and usually also on other 64 Bit systems + /// This allows to write portable code with optional LFS support typedef struct ::__stat64 stat_t; /// \brief Typedef for the file info structure used in the POSIX stat call /// Resolves to `struct _stat` on Windows and `struct stat` otherwise @@ -39,8 +39,9 @@ namespace nowide { /// \cond INTERNAL namespace detail { + BOOST_NOWIDE_DECL int stat(const char* path, stat_t* buffer, size_t buffer_size); BOOST_NOWIDE_DECL int stat(const char* path, posix_stat_t* buffer, size_t buffer_size); - } + } // namespace detail /// \endcond /// @@ -48,7 +49,10 @@ namespace nowide { /// /// Return information about a file from an UTF-8 encoded path /// - BOOST_NOWIDE_DECL int stat(const char* path, stat_t* buffer); + inline int stat(const char* path, stat_t* buffer) + { + return detail::stat(path, buffer, sizeof(*buffer)); + } /// /// \brief UTF-8 aware stat function, returns 0 on success /// diff --git a/include/boost/nowide/utf/convert.hpp b/include/boost/nowide/utf/convert.hpp index 242b7d12..2a9cda0f 100644 --- a/include/boost/nowide/utf/convert.hpp +++ b/include/boost/nowide/utf/convert.hpp @@ -1,11 +1,10 @@ // -// Copyright (c) 2012 Artyom Beilis (Tonkikh) -// Copyright (c) 2020 Alexander Grund -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2012 Artyom Beilis (Tonkikh) +// Copyright (c) 2020 Alexander Grund // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + #ifndef BOOST_NOWIDE_UTF_CONVERT_HPP_INCLUDED #define BOOST_NOWIDE_UTF_CONVERT_HPP_INCLUDED @@ -56,7 +55,7 @@ namespace nowide { size_t width = utf_traits::width(c); if(buffer_size < width) { - rv = NULL; + rv = nullptr; break; } buffer = utf_traits::encode(c, buffer); @@ -91,6 +90,17 @@ namespace nowide { return result; } + /// Convert the UTF sequence in the input string from \a CharIn to \a CharOut + /// and return it as a string + /// + /// Any illegal sequences are replaced with the replacement character, see #BOOST_NOWIDE_REPLACEMENT_CHARACTER + /// \tparam CharOut Output character type + template + std::basic_string convert_string(const std::basic_string& s) + { + return convert_string(s.data(), s.data() + s.size()); + } + } // namespace utf } // namespace nowide } // namespace boost diff --git a/include/boost/nowide/utf/utf.hpp b/include/boost/nowide/utf/utf.hpp index acdc57a5..fdb79acf 100644 --- a/include/boost/nowide/utf/utf.hpp +++ b/include/boost/nowide/utf/utf.hpp @@ -1,11 +1,10 @@ // -// Copyright (c) 2009-2011 Artyom Beilis (Tonkikh) -// Copyright (c) 2020 Alexander Grund -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2009-2011 Artyom Beilis (Tonkikh) +// Copyright (c) 2020 Alexander Grund // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + #ifndef BOOST_NOWIDE_UTF_HPP_INCLUDED #define BOOST_NOWIDE_UTF_HPP_INCLUDED diff --git a/include/boost/nowide/utf8_codecvt.hpp b/include/boost/nowide/utf8_codecvt.hpp index ebe0bccc..c61c138d 100644 --- a/include/boost/nowide/utf8_codecvt.hpp +++ b/include/boost/nowide/utf8_codecvt.hpp @@ -1,11 +1,10 @@ // -// Copyright (c) 2015 Artyom Beilis (Tonkikh) -// Copyright (c) 2020 Alexander Grund -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2015 Artyom Beilis (Tonkikh) +// Copyright (c) 2020 Alexander Grund // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + #ifndef BOOST_NOWIDE_UTF8_CODECVT_HPP_INCLUDED #define BOOST_NOWIDE_UTF8_CODECVT_HPP_INCLUDED diff --git a/include/boost/nowide/windows.hpp b/include/boost/nowide/windows.hpp index a5810be8..9d32f22f 100644 --- a/include/boost/nowide/windows.hpp +++ b/include/boost/nowide/windows.hpp @@ -1,30 +1,36 @@ // -// Copyright (c) 2012 Artyom Beilis (Tonkikh) -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2012 Artyom Beilis (Tonkikh) +// Copyright (c) 2022 Alexander Grund // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + #ifndef BOOST_NOWIDE_WINDOWS_HPP_INCLUDED #define BOOST_NOWIDE_WINDOWS_HPP_INCLUDED #ifdef BOOST_USE_WINDOWS_H #include +// (Usually) included by windows.h +#include #else -// -// These are function prototypes... Allow to avoid including windows.h -// +// When BOOST_USE_WINDOWS_H is not defined we declare the function prototypes to avoid including windows.h + extern "C" { +// From windows.h + __declspec(dllimport) wchar_t* __stdcall GetEnvironmentStringsW(void); __declspec(dllimport) int __stdcall FreeEnvironmentStringsW(wchar_t*); __declspec(dllimport) wchar_t* __stdcall GetCommandLineW(void); -__declspec(dllimport) wchar_t** __stdcall CommandLineToArgvW(const wchar_t*, int*); __declspec(dllimport) unsigned long __stdcall GetLastError(); __declspec(dllimport) void* __stdcall LocalFree(void*); __declspec(dllimport) int __stdcall SetEnvironmentVariableW(const wchar_t*, const wchar_t*); __declspec(dllimport) unsigned long __stdcall GetEnvironmentVariableW(const wchar_t*, wchar_t*, unsigned long); + +// From shellapi.h + +__declspec(dllimport) wchar_t** __stdcall CommandLineToArgvW(const wchar_t*, int*); } #endif diff --git a/src/console_buffer.cpp b/src/console_buffer.cpp index f92079be..5b6cbe26 100644 --- a/src/console_buffer.cpp +++ b/src/console_buffer.cpp @@ -1,9 +1,8 @@ -// Copyright (c) 2012 Artyom Beilis (Tonkikh) -// Copyright (c) 2020 - 2021 Alexander Grund +// Copyright (c) 2012 Artyom Beilis (Tonkikh) +// Copyright (c) 2020 - 2021 Alexander Grund // -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt #define BOOST_NOWIDE_SOURCE #include "console_buffer.hpp" diff --git a/src/console_buffer.hpp b/src/console_buffer.hpp index 65d01f2d..0074b384 100644 --- a/src/console_buffer.hpp +++ b/src/console_buffer.hpp @@ -1,9 +1,8 @@ -// Copyright (c) 2012 Artyom Beilis (Tonkikh) -// Copyright (c) 2020 - 2021 Alexander Grund +// Copyright (c) 2012 Artyom Beilis (Tonkikh) +// Copyright (c) 2020 - 2021 Alexander Grund // -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt #ifndef BOOST_NOWIDE_DETAIL_CONSOLE_BUFFER_HPP_INCLUDED #define BOOST_NOWIDE_DETAIL_CONSOLE_BUFFER_HPP_INCLUDED diff --git a/src/cstdio.cpp b/src/cstdio.cpp index 1e1672c6..2ff9574c 100644 --- a/src/cstdio.cpp +++ b/src/cstdio.cpp @@ -1,20 +1,23 @@ // -// Copyright (c) 2012 Artyom Beilis (Tonkikh) -// Copyright (c) 2020 Alexander Grund -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2012 Artyom Beilis (Tonkikh) +// Copyright (c) 2020-2022 Alexander Grund // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt #define BOOST_NOWIDE_SOURCE #ifdef _MSC_VER +#ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS -#elif(defined(__MINGW32__) || defined(__CYGWIN__)) && defined(__STRICT_ANSI__) -// Need the _w* functions which are extensions on MinGW/Cygwin +#endif +#elif defined(__MINGW32__) && defined(__STRICT_ANSI__) +// Need the _w* functions which are extensions on MinGW but not on MinGW-w64 +#include <_mingw.h> +#ifndef __MINGW64_VERSION_MAJOR #undef __STRICT_ANSI__ #endif +#endif #include #include diff --git a/src/cstdlib.cpp b/src/cstdlib.cpp index 067d389a..81533cdf 100644 --- a/src/cstdlib.cpp +++ b/src/cstdlib.cpp @@ -1,24 +1,30 @@ // -// Copyright (c) 2012 Artyom Beilis (Tonkikh) -// Copyright (c) 2020 Alexander Grund -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2012 Artyom Beilis (Tonkikh) +// Copyright (c) 2020-2022 Alexander Grund // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt #define BOOST_NOWIDE_SOURCE #ifdef _MSC_VER +#ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS -#elif(defined(__MINGW32__) || defined(__CYGWIN__)) && defined(__STRICT_ANSI__) -// Need the _w* functions which are extensions on MinGW/Cygwin +#endif +#elif defined(__MINGW32__) && defined(__STRICT_ANSI__) +// Need the _w* functions which are extensions on MinGW but not on MinGW-w64 +#include <_mingw.h> +#ifndef __MINGW64_VERSION_MAJOR #undef __STRICT_ANSI__ #endif +#elif defined(__CYGWIN__) && !defined(_GNU_SOURCE) +// The setenv family of functions is an extension on Cygwin +#define _GNU_SOURCE 1 +#endif #include -#if !defined(BOOST_WINDOWS) +#ifndef BOOST_WINDOWS namespace boost { namespace nowide { int setenv(const char* key, const char* value, int overwrite) @@ -42,11 +48,46 @@ namespace nowide { #include #include +namespace { +// thread_local was broken on MinGW for all 32bit compiler releases prior to 11.x, see +// https://sourceforge.net/p/mingw-w64/bugs/527/ +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=83562 +// Using a non-trivial destructor causes program termination on thread exit. +#if defined(__MINGW32__) && !defined(__MINGW64__) && !defined(__clang__) && (__GNUC__ < 11) +class stackstring_for_thread +{ + union + { + boost::nowide::stackstring s_; + }; + +public: + stackstring_for_thread() : s_(){}; + // Empty destructor so the union member (using a non-trivial destructor) does not get destroyed. + // This will leak memory if any is allocated by the stackstring for each terminated thread + // but as most values fit into the stack buffer this is rare and still better than a crash. + ~stackstring_for_thread(){}; + void convert(const wchar_t* begin, const wchar_t* end) + { + s_.convert(begin, end); + } + + char* get() + { + return s_.get(); + } +}; +#else +using stackstring_for_thread = boost::nowide::stackstring; +#endif + +} // namespace + namespace boost { namespace nowide { char* getenv(const char* key) { - static stackstring value; + thread_local stackstring_for_thread value; const wshort_stackstring name(key); @@ -66,7 +107,7 @@ namespace nowide { return 0; ptr = &tmp[0]; } - value.convert(ptr); + value.convert(ptr, ptr + n); return value.get(); } diff --git a/src/filebuf.cpp b/src/filebuf.cpp index 50388db1..aea4e784 100644 --- a/src/filebuf.cpp +++ b/src/filebuf.cpp @@ -1,10 +1,8 @@ // -// Copyright (c) 2020 Alexander Grund -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2020 Alexander Grund // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt #define BOOST_NOWIDE_SOURCE diff --git a/src/iostream.cpp b/src/iostream.cpp index be256c15..7171226f 100644 --- a/src/iostream.cpp +++ b/src/iostream.cpp @@ -1,9 +1,8 @@ -// Copyright (c) 2012 Artyom Beilis (Tonkikh) -// Copyright (c) 2020-2021 Alexander Grund +// Copyright (c) 2012 Artyom Beilis (Tonkikh) +// Copyright (c) 2020-2021 Alexander Grund // -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt #define BOOST_NOWIDE_SOURCE #include @@ -83,27 +82,32 @@ namespace nowide { } }; - winconsole_ostream::winconsole_ostream(const bool isBuffered, winconsole_ostream* tieStream) : std::ostream(0) + winconsole_ostream::winconsole_ostream(const target_stream target, + const bool isBuffered, + winconsole_ostream* tieStream) : + std::ostream(nullptr) { - HANDLE h; - if(isBuffered) - h = GetStdHandle(STD_OUTPUT_HANDLE); - else - h = GetStdHandle(STD_ERROR_HANDLE); + const HANDLE h = GetStdHandle(target == target_stream::output ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE); + if(is_atty_handle(h)) { d.reset(new console_output_buffer(h)); - std::ostream::rdbuf(d.get()); + rdbuf(d.get()); } else { - std::ostream::rdbuf(isBuffered ? std::cout.rdbuf() : std::cerr.rdbuf()); - assert(rdbuf()); + switch(target) + { + case target_stream::error: rdbuf(std::cerr.rdbuf()); break; + case target_stream::log: rdbuf(std::clog.rdbuf()); break; + case target_stream::output: rdbuf(std::cout.rdbuf()); break; + } } + assert(rdbuf()); + if(tieStream) - { tie(tieStream); - setf(ios_base::unitbuf); // If tieStream is set, this is cerr -> set unbuffered - } + if(!isBuffered) + setf(ios_base::unitbuf); } winconsole_ostream::~winconsole_ostream() { @@ -135,7 +139,7 @@ namespace nowide { } // namespace detail - // Make sure those are initialized as early as possible +// Make sure those are initialized as early as possible #ifdef BOOST_MSVC #pragma warning(disable : 4073) #pragma init_seg(lib) @@ -145,10 +149,11 @@ namespace nowide { #else #define BOOST_NOWIDE_INIT_PRIORITY #endif - detail::winconsole_ostream cout BOOST_NOWIDE_INIT_PRIORITY(true, nullptr); + using target_stream = detail::winconsole_ostream::target_stream; + detail::winconsole_ostream cout BOOST_NOWIDE_INIT_PRIORITY(target_stream::output, true, nullptr); detail::winconsole_istream cin BOOST_NOWIDE_INIT_PRIORITY(&cout); - detail::winconsole_ostream cerr BOOST_NOWIDE_INIT_PRIORITY(false, &cout); - detail::winconsole_ostream clog BOOST_NOWIDE_INIT_PRIORITY(false, nullptr); + detail::winconsole_ostream cerr BOOST_NOWIDE_INIT_PRIORITY(target_stream::error, false, &cout); + detail::winconsole_ostream clog BOOST_NOWIDE_INIT_PRIORITY(target_stream::log, true, nullptr); } // namespace nowide } // namespace boost diff --git a/src/stat.cpp b/src/stat.cpp index de1fa903..117ede35 100644 --- a/src/stat.cpp +++ b/src/stat.cpp @@ -1,20 +1,22 @@ // -// Copyright (c) 2020 Alexander Grund -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2020-2022 Alexander Grund // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt #define BOOST_NOWIDE_SOURCE -#if(defined(__MINGW32__) || defined(__CYGWIN__)) && defined(__STRICT_ANSI__) -// Need the _w* functions which are extensions on MinGW/Cygwin +#if defined(__MINGW32__) && defined(__STRICT_ANSI__) +// Need the _w* functions which are extensions on MinGW but not on MinGW-w64 +#include <_mingw.h> +#ifndef __MINGW64_VERSION_MAJOR #undef __STRICT_ANSI__ #endif +#endif #include -#if defined(BOOST_WINDOWS) +#ifdef BOOST_WINDOWS #include #include @@ -33,12 +35,17 @@ namespace nowide { const wstackstring wpath(path); return _wstat(wpath.get(), buffer); } + int stat(const char* path, stat_t* buffer, size_t buffer_size) + { + if(sizeof(*buffer) != buffer_size) + { + errno = EINVAL; + return EINVAL; + } + const wstackstring wpath(path); + return _wstat64(wpath.get(), buffer); + } } // namespace detail - int stat(const char* path, stat_t* buffer) - { - const wstackstring wpath(path); - return _wstat64(wpath.get(), buffer); - } } // namespace nowide } // namespace boost diff --git a/standalone/config.hpp b/standalone/config.hpp index 2800da02..cbb4cfb2 100644 --- a/standalone/config.hpp +++ b/standalone/config.hpp @@ -1,22 +1,18 @@ // -// Copyright (c) 2012 Artyom Beilis (Tonkikh) -// Copyright (c) 2020 Alexander Grund +// Copyright (c) 2012 Artyom Beilis (Tonkikh) +// Copyright (c) 2020 - 2022 Alexander Grund // -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) -// -#ifndef NOWIDE_CONFIG_HPP_INCLUDED -#define NOWIDE_CONFIG_HPP_INCLUDED +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -#include +// Defines macros which otherwise get defined by including -#if(defined(__WIN32) || defined(_WIN32) || defined(WIN32)) && !defined(__CYGWIN__) -#define NOWIDE_WINDOWS +#if(defined(_WIN32) || defined(__WIN32__) || defined(WIN32)) && !defined(__CYGWIN__) +#define BOOST_WINDOWS #endif #ifdef _MSC_VER -#define NOWIDE_MSVC _MSC_VER +#define BOOST_MSVC _MSC_VER #endif #ifdef __GNUC__ @@ -27,43 +23,17 @@ #define BOOST_SYMBOL_VISIBLE #endif -#ifdef NOWIDE_WINDOWS +#ifdef BOOST_WINDOWS #define BOOST_SYMBOL_EXPORT __declspec(dllexport) #define BOOST_SYMBOL_IMPORT __declspec(dllimport) +#elif defined(__CYGWIN__) && defined(__GNUC__) && (__GNUC__ >= 4) +#define BOOST_SYMBOL_EXPORT __attribute__((__dllexport__)) +#define BOOST_SYMBOL_IMPORT __attribute__((__dllimport__)) #else #define BOOST_SYMBOL_EXPORT BOOST_SYMBOL_VISIBLE #define BOOST_SYMBOL_IMPORT #endif -#if defined(BOOST_NOWIDE_DYN_LINK) -#ifdef BOOST_NOWIDE_SOURCE -#define BOOST_NOWIDE_DECL BOOST_SYMBOL_EXPORT -#else -#define BOOST_NOWIDE_DECL BOOST_SYMBOL_IMPORT -#endif // BOOST_NOWIDE_SOURCE -#else -#define BOOST_NOWIDE_DECL -#endif // BOOST_NOWIDE_DYN_LINK - -#ifndef NOWIDE_DECL -#define NOWIDE_DECL -#endif - -#if defined(NOWIDE_WINDOWS) -#ifdef BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT -#undef BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT -#endif -#define BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT 1 -#elif !defined(BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT) -#define BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT 0 -#endif - -#if defined(__GNUC__) && __GNUC__ >= 7 -#define BOOST_NOWIDE_FALLTHROUGH __attribute__((fallthrough)) -#else -#define BOOST_NOWIDE_FALLTHROUGH -#endif - #if defined __GNUC__ #define BOOST_LIKELY(x) __builtin_expect(x, 1) #define BOOST_UNLIKELY(x) __builtin_expect(x, 0) @@ -75,23 +45,3 @@ #define BOOST_UNLIKELY(x) x #endif #endif - -// The std::codecvt are deprecated in C++20 -// These macros can suppress this warning -#if defined(_MSC_VER) -#define BOOST_NOWIDE_SUPPRESS_UTF_CODECVT_DEPRECATION_BEGIN __pragma(warning(push)) __pragma(warning(disable : 4996)) -#define BOOST_NOWIDE_SUPPRESS_UTF_CODECVT_DEPRECATION_END __pragma(warning(pop)) -#elif(__cplusplus >= 202002L) && defined(__clang__) -#define BOOST_NOWIDE_SUPPRESS_UTF_CODECVT_DEPRECATION_BEGIN \ - _Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") -#define BOOST_NOWIDE_SUPPRESS_UTF_CODECVT_DEPRECATION_END _Pragma("clang diagnostic pop") -#elif(__cplusplus >= 202002L) && defined(__GNUC__) -#define BOOST_NOWIDE_SUPPRESS_UTF_CODECVT_DEPRECATION_BEGIN \ - _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") -#define BOOST_NOWIDE_SUPPRESS_UTF_CODECVT_DEPRECATION_END _Pragma("GCC diagnostic pop") -#else -#define BOOST_NOWIDE_SUPPRESS_UTF_CODECVT_DEPRECATION_BEGIN -#define BOOST_NOWIDE_SUPPRESS_UTF_CODECVT_DEPRECATION_END -#endif - -#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index de7fd653..9565b008 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,18 +1,25 @@ -# Copyright 2019 - 2021 Alexander Grund +# Copyright 2019 - 2024 Alexander Grund # Distributed under the Boost Software License, Version 1.0. -# (See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt) +# https://www.boost.org/LICENSE_1_0.txt include(CheckCXXCompilerFlag) check_cxx_compiler_flag(-Wsuggest-override _BOOST_NOWIDE_SUGGEST_OVERRIDE_SUPPORTED) -add_library(boost_nowide_file_test_helpers STATIC file_test_helpers.cpp) -target_link_libraries(boost_nowide_file_test_helpers PRIVATE PRIVATE Boost::nowide) +add_library(boost_nowide_file_test_helpers STATIC EXCLUDE_FROM_ALL file_test_helpers.cpp) +target_link_libraries(boost_nowide_file_test_helpers PRIVATE Boost::nowide) target_compile_definitions(boost_nowide_file_test_helpers PRIVATE BOOST_ALL_NO_LIB) if(NOT TARGET tests) add_custom_target(tests) endif() +if(NOT BOOST_SUPERPROJECT_SOURCE_DIR) + find_package(Boost 1.56 REQUIRED COMPONENTS filesystem system) +endif() + +# In some environments this test (part) may fail, so allow to disable it +option(BOOST_NOWIDE_DISABLE_CIN_TEST "Disable integration test using console input" OFF) + function(boost_nowide_add_test name) cmake_parse_arguments(PARSE_ARGV 1 ARG "COMPILE_ONLY" "SRC" "LIBRARIES;DEFINITIONS;ARGS") if(NOT ARG_SRC) @@ -20,7 +27,7 @@ function(boost_nowide_add_test name) endif() set(name ${PROJECT_NAME}-${name}) - add_executable(${name} ${ARG_SRC}) + add_executable(${name} EXCLUDE_FROM_ALL ${ARG_SRC}) add_dependencies(tests ${name}) target_link_libraries(${name} PRIVATE Boost::nowide ${ARG_LIBRARIES}) boost_add_warnings(${name} pedantic ${Boost_NOWIDE_WERROR}) @@ -37,12 +44,16 @@ boost_nowide_add_test(test_codecvt) boost_nowide_add_test(test_convert) boost_nowide_add_test(test_env) boost_nowide_add_test(test_env_win SRC test_env.cpp DEFINITIONS BOOST_NOWIDE_TEST_INCLUDE_WINDOWS) +boost_nowide_add_test(test_fs LIBRARIES Boost::filesystem) boost_nowide_add_test(test_filebuf LIBRARIES boost_nowide_file_test_helpers) boost_nowide_add_test(test_ifstream LIBRARIES boost_nowide_file_test_helpers) boost_nowide_add_test(test_ofstream LIBRARIES boost_nowide_file_test_helpers) boost_nowide_add_test(test_fstream LIBRARIES boost_nowide_file_test_helpers) boost_nowide_add_test(test_fstream_special LIBRARIES boost_nowide_file_test_helpers) boost_nowide_add_test(test_iostream LIBRARIES boost_nowide_file_test_helpers) +if(BOOST_NOWIDE_DISABLE_CIN_TEST) + target_compile_definitions(${PROJECT_NAME}-test_iostream PRIVATE BOOST_NOWIDE_DISABLE_CIN_TEST) +endif() boost_nowide_add_test(test_iostream_interactive COMPILE_ONLY SRC test_iostream.cpp DEFINITIONS BOOST_NOWIDE_TEST_INTERACTIVE LIBRARIES boost_nowide_file_test_helpers) boost_nowide_add_test(test_stackstring) boost_nowide_add_test(test_stat) @@ -50,12 +61,14 @@ boost_nowide_add_test(test_stdio) boost_nowide_add_test(test_system_n SRC test_system.cpp DEFINITIONS BOOST_NOWIDE_TEST_USE_NARROW=1) if(WIN32) boost_nowide_add_test(test_system_w SRC test_system.cpp DEFINITIONS BOOST_NOWIDE_TEST_USE_NARROW=0) + boost_nowide_add_test(test_system_use_windows_h SRC test_system.cpp DEFINITIONS BOOST_NOWIDE_TEST_USE_NARROW=0 BOOST_USE_WINDOWS_H) + boost_nowide_add_test(test_system_use_windows_h_lean SRC test_system.cpp DEFINITIONS BOOST_NOWIDE_TEST_USE_NARROW=0 BOOST_USE_WINDOWS_H WIN32_LEAN_AND_MEAN) else() foreach(test test_filebuf test_ifstream test_ofstream test_fstream test_fstream_special) boost_nowide_add_test(${test}_internal SRC ${test}.cpp DEFINITIONS BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT=1 LIBRARIES boost_nowide_file_test_helpers) endforeach() endif() -boost_nowide_add_test(test_traits) +boost_nowide_add_test(test_traits DEFINITIONS BOOST_NOWIDE_TEST_BFS_PATH LIBRARIES Boost::filesystem) # Test that passthrough writes everything from stdin to stdout # Needs to be done with CMake as the test driver to write any input to stdin and check output @@ -66,9 +79,4 @@ add_test( -P ${CMAKE_CURRENT_SOURCE_DIR}/test_iostream_passthrough.cmake ) -if(NOT BOOST_SUPERPROJECT_SOURCE_DIR) - find_package(Boost 1.56 REQUIRED COMPONENTS filesystem system) -endif() -boost_nowide_add_test(test_fs LIBRARIES Boost::filesystem) -boost_nowide_add_test(test_traits_fs SRC test_traits.cpp LIBRARIES Boost::filesystem DEFINITIONS BOOST_NOWIDE_TEST_BFS_PATH) boost_nowide_add_test(benchmark_fstream COMPILE_ONLY DEFINITIONS BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT=1) diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 718c999e..f632ed58 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -1,12 +1,9 @@ -# Boost Nowide Library test Jamfile - # Copyright (c) 2003, 2006 Beman Dawes # Copyright (c) 2012 Artyom Beilis (Tonkikh) -# Copyright (c) 2020-2021 Alexander Grund +# Copyright (c) 2020-2022 Alexander Grund # # Distributed under the Boost Software License, Version 1.0. -# (See accompanying file LICENSE or www.boost.org/LICENSE_1_0.txt) -# See library home page at http://www.boost.org/libs/nowide +# https://www.boost.org/LICENSE_1_0.txt import testing ; import config : requires ; @@ -23,19 +20,15 @@ rule require-windows ( properties * ) project : requirements /boost/nowide//boost_nowide + . pedantic on - [ requires - cxx11_defaulted_functions - cxx11_noexcept - cxx11_rvalue_references - cxx11_static_assert - ] [ check-target-builds ../config//cxx11_moveable_fstreams "std::fstream is moveable and swappable" : : no ] ; lib shell32 ; lib file_test_helpers : file_test_helpers.cpp : static -/boost/nowide//boost_nowide ; +explicit file_test_helpers ; run test_codecvt.cpp ; run test_convert.cpp ; @@ -64,7 +57,9 @@ run test_stackstring.cpp ; run test_stat.cpp ; run test_stdio.cpp ; run test_system.cpp : : : BOOST_NOWIDE_TEST_USE_NARROW=1 windows:shell32 darwin,shared:no : test_system_n ; -run test_system.cpp : : : BOOST_NOWIDE_TEST_USE_NARROW=0 windows:shell32 @require-windows : test_system_w ; +run test_system.cpp : : : BOOST_NOWIDE_TEST_USE_NARROW=0 shell32 @require-windows : test_system_w ; +run test_system.cpp : : : BOOST_NOWIDE_TEST_USE_NARROW=0 BOOST_USE_WINDOWS_H shell32 @require-windows : test_system_use_windows_h ; +run test_system.cpp : : : BOOST_NOWIDE_TEST_USE_NARROW=0 BOOST_USE_WINDOWS_H WIN32_LEAN_AND_MEAN shell32 @require-windows : test_system_use_windows_h_lean ; run test_traits.cpp : : : BOOST_NOWIDE_TEST_BFS_PATH /boost/filesystem//boost_filesystem/off ; compile benchmark_fstream.cpp : BOOST_NOWIDE_USE_WIN_FSTREAM=1 [ requires cxx11_hdr_chrono ] ; diff --git a/test/benchmark_fstream.cpp b/test/benchmark_fstream.cpp index 0acf7081..e464d0cf 100644 --- a/test/benchmark_fstream.cpp +++ b/test/benchmark_fstream.cpp @@ -1,11 +1,9 @@ // -// Copyright (c) 2012 Artyom Beilis (Tonkikh) -// Copyright (c) 2019 - 2020 Alexander Grund -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2012 Artyom Beilis (Tonkikh) +// Copyright (c) 2019 - 2020 Alexander Grund // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt #define BOOST_NOWIDE_TEST_NO_MAIN @@ -249,8 +247,6 @@ void print_perf_data(const blocksize_to_performance& stdio_data, void test_perf(const char* file) { - perf_data nowide_data_txt2 = test_io_driver>(file, "std::fstream", false); - /* perf_data stdio_data = test_io_driver(file, "stdio", true); perf_data std_data = test_io_driver>(file, "std::fstream", true); perf_data nowide_data = test_io_driver>(file, "nowide::fstream", true); @@ -265,7 +261,6 @@ void test_perf(const char* file) print_perf_data(stdio_data_txt.read, std_data_txt.read, nowide_data_txt.read); std::cout << "================== Write performance (text) ===================" << std::endl; print_perf_data(stdio_data_txt.write, std_data_txt.write, nowide_data_txt.write); - */ } int main(int argc, char** argv) @@ -282,7 +277,7 @@ int main(int argc, char** argv) try { test_perf(filename.c_str()); - } catch(const std::runtime_error& err) + } catch(const std::exception& err) { std::cerr << "Benchmarking failed: " << err.what() << std::endl; return 1; diff --git a/test/cmake_test/CMakeLists.txt b/test/cmake_test/CMakeLists.txt index 11d5d98c..942a73ff 100644 --- a/test/cmake_test/CMakeLists.txt +++ b/test/cmake_test/CMakeLists.txt @@ -1,6 +1,7 @@ # Copyright 2021 Alexander Grund +# # Distributed under the Boost Software License, Version 1.0. -# See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt +# https://www.boost.org/LICENSE_1_0.txt cmake_minimum_required(VERSION 3.5...3.16) diff --git a/test/cmake_test/main.cpp b/test/cmake_test/main.cpp index 36ccb89e..097bf1a2 100644 --- a/test/cmake_test/main.cpp +++ b/test/cmake_test/main.cpp @@ -1,10 +1,8 @@ // -// Copyright (c) 2019-2021 Alexander Grund -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2019-2021 Alexander Grund // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt #include #include diff --git a/test/file_test_helpers.cpp b/test/file_test_helpers.cpp index 3835d4a3..65e3d7ee 100644 --- a/test/file_test_helpers.cpp +++ b/test/file_test_helpers.cpp @@ -1,8 +1,7 @@ -// Copyright (c) 2019-2021 Alexander Grund +// Copyright (c) 2019-2021 Alexander Grund // -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt #define BOOST_NOWIDE_TEST_NO_MAIN #include "file_test_helpers.hpp" @@ -10,6 +9,7 @@ #include #include "test.hpp" #include +#include #include #include #include @@ -76,9 +76,16 @@ namespace nowide { return result; } + static std::minstd_rand make_rand_engine() + { + const auto seed = std::random_device{}(); + std::cout << "RNG seed: " << seed << std::endl; + return std::minstd_rand(seed); + } + std::string create_random_data(size_t num_chars, data_type type) { - static std::minstd_rand rng(std::random_device{}()); + static std::minstd_rand rng = make_rand_engine(); std::string result(num_chars, '\0'); if(type == data_type::binary) { diff --git a/test/file_test_helpers.hpp b/test/file_test_helpers.hpp index 0902ee09..e2483216 100644 --- a/test/file_test_helpers.hpp +++ b/test/file_test_helpers.hpp @@ -1,8 +1,7 @@ -// Copyright (c) 2019-2021 Alexander Grund +// Copyright (c) 2019-2021 Alexander Grund // -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt #ifndef BOOST_NOWIDE_FILE_TEST_HELPERS_HPP_INCLUDED #define BOOST_NOWIDE_FILE_TEST_HELPERS_HPP_INCLUDED diff --git a/test/test.hpp b/test/test.hpp index 31dc7f56..f5a8ec62 100644 --- a/test/test.hpp +++ b/test/test.hpp @@ -1,11 +1,10 @@ // -// Copyright (c) 2012 Artyom Beilis (Tonkikh) -// Copyright (c) 2019-2020 Alexander Grund -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2012 Artyom Beilis (Tonkikh) +// Copyright (c) 2019-2020 Alexander Grund // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + #ifndef BOOST_NOWIDE_LIB_TEST_H_INCLUDED #define BOOST_NOWIDE_LIB_TEST_H_INCLUDED @@ -42,17 +41,33 @@ namespace nowide { _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); #endif } + std::string context; }; inline test_monitor& test_mon() { static test_monitor instance; return instance; } + struct context + { + std::string oldCtx; + context(std::string ctx) : oldCtx(std::move(test_mon().context)) + { + test_mon().context = std::move(ctx); + } + ~context() + { + test_mon().context = std::move(oldCtx); + } + }; /// Function called when a test failed to be able set a breakpoint for debugging inline void test_failed(const char* expr, const char* file, const int line, const char* function) { std::ostringstream ss; ss << expr << " at " << file << ':' << line << " in " << function; + std::string& ctx = test_mon().context; + if(!ctx.empty()) + ss << " context: " << ctx; throw test_error(ss.str()); } @@ -72,7 +87,7 @@ namespace nowide { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wtautological-constant-out-of-range-compare" #endif - if(c >= std::numeric_limits::min() && c <= std::numeric_limits::max()) + if(c >= (std::numeric_limits::min)() && c <= (std::numeric_limits::max)()) ss << static_cast(c); else ss << "\\" << std::setw(sizeof(wchar_t) * 2) << static_cast(c) << std::setw(defWidth); @@ -139,6 +154,10 @@ namespace nowide { DISABLE_CONST_EXPR_DETECTED \ } while(0) DISABLE_CONST_EXPR_DETECTED_POP +#define TEST_CONTEXT(expr) \ + boost::nowide::test::context _##__COUNTER__( \ + static_cast(std::stringstream{} << expr).str()) + #ifndef BOOST_NOWIDE_TEST_NO_MAIN // Tests should implement this void test_main(int argc, char** argv, char** env); diff --git a/test/test_codecvt.cpp b/test/test_codecvt.cpp index a793299a..ff5dbdbf 100644 --- a/test/test_codecvt.cpp +++ b/test/test_codecvt.cpp @@ -1,10 +1,8 @@ // -// Copyright (c) 2015 Artyom Beilis (Tonkikh) -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2015 Artyom Beilis (Tonkikh) // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt #include @@ -295,7 +293,15 @@ void test_codecvt_err() TEST_EQ(cvt.in(mb, from, from_end, from_next, to, to_end, to_next), cvt_type::partial); TEST(from_next == from + 1); TEST(to_next == to + 1); + // False positive in GCC 13 in MinGW +#if defined(__GNUC__) && __GNUC__ >= 13 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfree-nonheap-object" +#endif TEST(std::wstring(to, to_next) == std::wstring(L"1")); +#if defined(__GNUC__) && __GNUC__ >= 13 +#pragma GCC diagnostic pop +#endif } { char buf[4] = {}; @@ -334,7 +340,7 @@ void test_codecvt_err() char* to = buf; char* to_end = buf + 32; char* to_next = to; - wchar_t err_buf[3] = {'1', 0xDC9E, 0}; // second surrogate not works both for UTF-16 and 32 + wchar_t err_buf[] = {'1', 0xDC9E, 0}; // second value is invalid for UTF-16 and 32 const wchar_t* err_utf = err_buf; { std::mbstate_t mb{}; @@ -344,7 +350,7 @@ void test_codecvt_err() TEST_EQ(cvt.out(mb, from, from_end, from_next, to, to_end, to_next), cvt_type::ok); TEST(from_next == from + 2); TEST(to_next == to + 4); - TEST_EQ(std::string(to, to_next), "1" + boost::nowide::narrow(wreplacement_str)); + TEST_EQ(std::string(to, to_next), boost::nowide::narrow(err_buf)); } } } @@ -411,7 +417,7 @@ void test_codecvt_subst() run_all(codecvt_to_wide, codecvt_to_narrow); } -// coverity [root_function] +// coverity[root_function] void test_main(int, char**, char**) { test_codecvt_basic(); diff --git a/test/test_convert.cpp b/test/test_convert.cpp index b0fe6c33..8f13e0aa 100644 --- a/test/test_convert.cpp +++ b/test/test_convert.cpp @@ -1,10 +1,8 @@ // -// Copyright (c) 2012 Artyom Beilis (Tonkikh) -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2012 Artyom Beilis (Tonkikh) // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt #include #include "test.hpp" @@ -127,7 +125,7 @@ std::string narrow_string_view(const std::wstring& s) } #endif -// coverity [root_function] +// coverity[root_function] void test_main(int, char**, char**) { std::string hello = "\xd7\xa9\xd7\x9c\xd7\x95\xd7\x9d"; diff --git a/test/test_env.cpp b/test/test_env.cpp index f3fdb749..0bc782be 100644 --- a/test/test_env.cpp +++ b/test/test_env.cpp @@ -1,10 +1,8 @@ // -// Copyright (c) 2012 Artyom Beilis (Tonkikh) -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2012 Artyom Beilis (Tonkikh) // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt #include #include "test.hpp" @@ -14,7 +12,7 @@ #include #endif -// coverity [root_function] +// coverity[root_function] void test_main(int, char**, char**) { std::string example = "\xd7\xa9-\xd0\xbc-\xce\xbd"; diff --git a/test/test_filebuf.cpp b/test/test_filebuf.cpp index 6a47db30..88ff9aa6 100644 --- a/test/test_filebuf.cpp +++ b/test/test_filebuf.cpp @@ -1,18 +1,19 @@ -// Copyright (c) 2015 Artyom Beilis (Tonkikh) -// Copyright (c) 2019-2021 Alexander Grund +// Copyright (c) 2015 Artyom Beilis (Tonkikh) +// Copyright (c) 2019-2021 Alexander Grund // -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt #include #include "file_test_helpers.hpp" #include "test.hpp" +#include #include #include #include #include +#include namespace nw = boost::nowide; using namespace boost::nowide::test; @@ -24,6 +25,52 @@ static_assert(std::is_same::value, "!"); static_assert(std::is_same::value, "!"); +using CharTraits = nw::filebuf::traits_type; +const auto eof = CharTraits::eof(); + +constexpr std::ios_base::openmode make_mode(std::ios_base::openmode flags, bool binary) +{ + return binary ? flags | std::ios_base::binary : flags; +} + +template +bool open_with_buffer(T_FileBuf& filebuf, + const std::string& filepath, + std::ios_base::openmode flags, + char* buffer, + size_t size) +{ + const bool bufferSet = filebuf.pubsetbuf(buffer, size) != nullptr; + return filebuf.open(filepath, flags) && (bufferSet || filebuf.pubsetbuf(buffer, size)); +} + +template +bool open_with_buffer(T_FileBuf& filebuf, + const std::string& filepath, + std::ios_base::openmode flags, + char (&buffer)[T_size]) +{ + return open_with_buffer(filebuf, filepath, flags, buffer, T_size); +} + +template +bool open_unbuffered(T_FileBuf& filebuf, const std::string& filepath, std::ios_base::openmode flags) +{ + return open_with_buffer(filebuf, filepath, flags, nullptr, 0); +} + +// Read the given number of chars from the buffer, checking that there are that many +template +bool skip_chars(T_Buf& buf, size_t num_chars) +{ + for(size_t i = 0; i < num_chars; ++i) + { + if(buf.sbumpc() == eof) + return false; // LCOV_EXCL_LINE + } + return true; +} + void test_open_close(const std::string& filepath) { const std::string filepath2 = filepath + ".2"; @@ -31,22 +78,62 @@ void test_open_close(const std::string& filepath) remove_file_at_exit _(filepath); remove_file_at_exit _2(filepath2); - nw::filebuf buf; - TEST(buf.open(filepath, std::ios_base::out) == &buf); - TEST(buf.is_open()); - - // Opening when already open fails - TEST(buf.open(filepath2, std::ios_base::out) == nullptr); - // Still open - TEST(buf.is_open()); - TEST(buf.close() == &buf); - // Failed opening did not create file - TEST(!file_exists(filepath2)); - - // But it should work now: - TEST(buf.open(filepath2, std::ios_base::out) == &buf); - TEST(buf.close() == &buf); - TEST(file_exists(filepath2)); + { + nw::filebuf buf; + TEST(buf.open(filepath, std::ios_base::out) == &buf); + TEST(buf.is_open()); + + // Opening when already open fails + TEST(buf.open(filepath2, std::ios_base::out) == nullptr); + // Still open + TEST(buf.is_open()); + TEST(buf.close() == &buf); + // Failed opening did not create file + TEST(!file_exists(filepath2)); + + // But it should work now: + TEST(buf.open(filepath2, std::ios_base::out) == &buf); + TEST(buf.close() == &buf); + TEST(file_exists(filepath2)); + } + + const auto file_data = create_random_data(20, data_type::text); + create_file(filepath, file_data); + // Can't write to read-only buf + { + nw::filebuf buf; + TEST(buf.open(filepath, std::ios_base::in)); + TEST_EQ(buf.sputc('a'), eof); + // Even if chars were copied to put area, they cannot be written (in sync) + TEST(buf.sputn("hello", 5) == 0 || buf.pubsync() == -1); + } + TEST_EQ(read_file(filepath), file_data); // File is unchanged + { + nw::filebuf buf; + char buffer[3]; + TEST(open_with_buffer(buf, filepath, std::ios_base::in, buffer)); + // Also doesn't write when using direct I/O due to data>buffersize (at least in Nowide) + TEST(buf.sputn("hello", 5) == 0 || buf.pubsync() == -1); + } + TEST_EQ(read_file(filepath), file_data); // File is unchanged + + // Can't read from write-only buf + { + for(const auto flags : {std::ios_base::out, std::ios_base::app}) + { + create_file(filepath, file_data); + nw::filebuf buf; + TEST(buf.open(filepath, flags)); + TEST_EQ(buf.sgetc(), eof); + TEST_EQ(buf.sbumpc(), eof); + char str[3]; + TEST_EQ(buf.sgetn(str, sizeof(str)), 0); + // Putback is also just for reading + TEST(buf.pubseekoff(0, std::ios_base::end) != std::streampos(-1)); + TEST_EQ(buf.sungetc(), eof); + TEST_EQ(buf.sputbackc('t'), eof); + } + } } void test_pubseekpos(const std::string& filepath) @@ -61,9 +148,8 @@ void test_pubseekpos(const std::string& filepath) using pos_type = nw::filebuf::pos_type; const auto eofPos = pos_type(data.size()); std::uniform_int_distribution distr(0, static_cast(eofPos) - 1); - using traits = nw::filebuf::traits_type; - const auto getData = [&](pos_type pos) { return traits::to_int_type(data[static_cast(pos)]); }; + const auto getData = [&](pos_type pos) { return CharTraits::to_int_type(data[static_cast(pos)]); }; for(int i = 0; i < 100; i++) { @@ -73,9 +159,9 @@ void test_pubseekpos(const std::string& filepath) } // Seek to first and last as corner case tests TEST_EQ(buf.pubseekpos(0), pos_type(0)); - TEST_EQ(buf.sgetc(), traits::to_int_type(data[0])); + TEST_EQ(buf.sgetc(), CharTraits::to_int_type(data[0])); TEST_EQ(buf.pubseekpos(eofPos), eofPos); - TEST_EQ(buf.sgetc(), traits::eof()); + TEST_EQ(buf.sgetc(), eof); } void test_pubseekoff(const std::string& filepath) @@ -91,9 +177,8 @@ void test_pubseekoff(const std::string& filepath) using off_type = nw::filebuf::off_type; const auto eofPos = pos_type(data.size()); std::uniform_int_distribution distr(0, static_cast(eofPos) - 1); - using traits = nw::filebuf::traits_type; - const auto getData = [&](pos_type pos) { return traits::to_int_type(data[static_cast(pos)]); }; + const auto getData = [&](pos_type pos) { return CharTraits::to_int_type(data[static_cast(pos)]); }; // tellg/tellp function as called by basic_[io]fstream const auto tellg = [&]() { return buf.pubseekoff(0, std::ios_base::cur); }; @@ -120,10 +205,10 @@ void test_pubseekoff(const std::string& filepath) // Seek to first and last as corner case tests TEST_EQ(buf.pubseekoff(0, std::ios_base::beg), pos_type(0)); TEST_EQ(tellg(), pos_type(0)); - TEST_EQ(buf.sgetc(), traits::to_int_type(data[0])); + TEST_EQ(buf.sgetc(), CharTraits::to_int_type(data[0])); TEST_EQ(buf.pubseekoff(0, std::ios_base::end), eofPos); TEST_EQ(tellg(), eofPos); - TEST_EQ(buf.sgetc(), traits::eof()); + TEST_EQ(buf.sgetc(), eof); } void test_64_bit_seek(const std::string& filepath) @@ -171,13 +256,424 @@ void test_64_bit_seek(const std::string& filepath) } } +void test_xsgetn(const std::string& filepath, bool binary) +{ + char buffer[200]{}; + const auto dataType = binary ? data_type::binary : data_type::text; + const std::string data = create_random_data(sizeof(buffer) + 50, dataType); + create_file(filepath, data, dataType); + + for(const bool unbuffered : {false, true}) + { + TEST_CONTEXT((unbuffered ? "unbuffered" : "buffered")); + nw::filebuf buf; + if(unbuffered) + TEST(open_unbuffered(buf, filepath, make_mode(std::ios_base::in, binary))); + else + TEST(open_with_buffer(buf, filepath, make_mode(std::ios_base::in, binary), buffer)); + + std::string strBuf(data.size() * 2, '\0'); + // Reading stops at EOF + TEST_EQ(buf.sgetn(&strBuf[0], strBuf.size()), static_cast(data.size())); + strBuf.resize(data.size()); + TEST_EQ(strBuf, data); + TEST(buf.pubseekpos(0) != std::streampos(-1)); + // Read a bit using regular underflow, then via sgetn and again via underflow + TEST_EQ(buf.sbumpc(), CharTraits::to_int_type(data[0])); + TEST_EQ(buf.sbumpc(), CharTraits::to_int_type(data[1])); + strBuf.clear(); + // Go definitely over a buffer boundary + strBuf.resize(sizeof(buffer)); + TEST_EQ(buf.sgetn(&strBuf[0], strBuf.size()), static_cast(strBuf.size())); + TEST_EQ(strBuf, data.substr(2, strBuf.size())); + TEST_EQ(buf.sbumpc(), CharTraits::to_int_type(data[strBuf.size() + 2])); + TEST_EQ(buf.sbumpc(), CharTraits::to_int_type(data[strBuf.size() + 3])); + } + // Corner cases: + // - sgetn with zero or negative count is a no-op + // - sgetn fails on closed filebuf + for(const bool unbuffered : {false, true}) + { + TEST_CONTEXT((unbuffered ? "unbuffered" : "buffered")); + create_file(filepath, "Hello World"); + nw::filebuf buf; + // Set a buffer just to see if it is written to + if(unbuffered) + TEST(open_unbuffered(buf, filepath, make_mode(std::ios_base::in, binary))); + else + TEST(open_with_buffer(buf, filepath, make_mode(std::ios_base::in, binary), buffer)); + + std::string str = create_random_data(data.size(), data_type::binary); + const auto origStr = str; + buffer[0] = origStr[0]; + + TEST_EQ(buf.sgetn(&str[0], 0), 0); +#if defined(__GNUC__) && __GNUC__ >= 12 + // GCC may not detect that the negative value is checked by xsgetn +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wrestrict" +#endif + // coverity[negative_returns] + TEST_EQ(buf.sgetn(&str[0], -1), 0); + // coverity[negative_returns] + TEST_EQ(buf.sgetn(&str[0], -999), 0); +#if defined(__GNUC__) && __GNUC__ >= 12 +#pragma GCC diagnostic pop +#endif + buf.close(); + // No read when closed + TEST_EQ(buf.sgetn(&str[0], 1), 0); + TEST_EQ(str, origStr); + TEST_EQ(buffer[0], origStr[0]); + TEST_EQ(buf.sgetn(&str[0], str.size()), 0); + TEST_EQ(str, origStr); + TEST_EQ(buffer[0], origStr[0]); + } +} + +void test_xsputn(const std::string& filepath, bool binary) +{ + char buffer[200]{}; + const auto dataType = binary ? data_type::binary : data_type::text; + const std::string data = create_random_data(sizeof(buffer) + 50, dataType); + + for(const bool unbuffered : {false, true}) + { + TEST_CONTEXT((unbuffered ? "unbuffered" : "buffered")); + nw::filebuf buf; + const auto flags = make_mode(std::ios_base::out | std::ios_base::trunc, binary); + if(unbuffered) + TEST(open_unbuffered(buf, filepath, flags)); + else + TEST(open_with_buffer(buf, filepath, flags, buffer)); + + TEST_EQ(buf.sputn(data.data(), data.size()), static_cast(data.size())); + buf.close(); + TEST_EQ(read_file(filepath, dataType), data); + + // Write a bit using regular overflow, then via sputn and back using overflow + TEST(buf.open(filepath, flags)); + TEST_EQ(buf.sputc(data[0]), CharTraits::to_int_type(data[0])); + TEST_EQ(buf.sputc(data[1]), CharTraits::to_int_type(data[1])); + // This is more than 1 buffer size + std::streamsize numBytesToWrite = data.size() - 4; + TEST_EQ(buf.sputn(&data[2], numBytesToWrite), numBytesToWrite); + TEST_EQ(buf.sputc(data[data.size() - 2]), CharTraits::to_int_type(data[data.size() - 2])); + TEST_EQ(buf.sputc(data[data.size() - 1]), CharTraits::to_int_type(data[data.size() - 1])); + buf.close(); + TEST_EQ(read_file(filepath, dataType), data); + } + // Corner cases: + // - sputn with zero or negative count is a no-op + // - sputn fails on closed filebuf + for(const bool unbuffered : {false, true}) + { + TEST_CONTEXT((unbuffered ? "unbuffered" : "buffered")); + nw::filebuf buf; + if(unbuffered) + TEST(open_unbuffered(buf, filepath, make_mode(std::ios_base::out | std::ios_base::trunc, binary))); + else + TEST(open_with_buffer(buf, filepath, make_mode(std::ios_base::out | std::ios_base::trunc, binary), buffer)); + + TEST_EQ(buf.sputn(data.data(), 0), 0); + // coverity[negative_returns] + TEST_EQ(buf.sputn(data.data(), -1), 0); + // coverity[negative_returns] + TEST_EQ(buf.sputn(data.data(), -999), 0); + buf.close(); + // No write when closed + TEST_EQ(buf.sputn(data.data(), 1), 0); + TEST_EQ(buf.sputn(data.data(), data.size()), 0); + TEST_EQ(read_file(filepath), ""); + } +} + +void test_read_write_switch(const std::string& filepath, bool binary) +{ + // Switching between read and write requires a seek or (for W->R) a sync + const std::string data = "1234567890"; + nw::filebuf buf; + TEST(buf.open(filepath, make_mode(std::ios_base::in | std::ios_base::out | std::ios_base::trunc, binary))); + TEST_EQ(buf.sputn(data.data(), data.size()), static_cast(data.size())); + // W->R via seek + buf.pubseekpos(0); + TEST_EQ(buf.sbumpc(), '1'); + // R->W via seek + const auto pos = buf.pubseekoff(0, std::ios_base::cur); + TEST(pos != std::streampos(-1)); + buf.sputc('b'); + // W->R via sync + TEST(buf.pubsync() == 0); + TEST_EQ(buf.sbumpc(), '3'); + // R->W via seek + const auto pos2 = buf.pubseekoff(0, std::ios_base::cur); + buf.sputc('c'); + // Read right back + TEST_EQ(buf.pubseekpos(pos2), pos2); + TEST_EQ(buf.sbumpc(), 'c'); + // R->W + buf.pubseekoff(0, std::ios_base::cur); + buf.sputc('d'); + // Sync & seek + TEST(buf.pubsync() == 0); + TEST(buf.pubseekoff(0, std::ios_base::cur) != std::streampos(-1)); + TEST_EQ(buf.sbumpc(), '6'); + // R->W + buf.pubseekoff(0, std::ios_base::cur); + buf.sputc('e'); + // Seek & sync + TEST(buf.pubseekoff(0, std::ios_base::cur) != std::streampos(-1)); + TEST(buf.pubsync() == 0); + TEST_EQ(buf.sbumpc(), '8'); + + buf.close(); + TEST_EQ(read_file(filepath), "1b3cd6e890"); +} + +void subtest_sync(const std::string& filepath, bool binary, const std::string& data) +{ + nw::filebuf buf; + // Use a small buffer to force filling it up w/o requiring to write lot's of data + char buffer[3]; + buf.pubsetbuf(buffer, sizeof(buffer)); + const auto flags = make_mode(std::ios_base::out | std::ios_base::trunc, binary); + + // Do a series of single-char and multi-char writes with varying size combinations + // Especially test the case of only single-char and only multi-char ops + for(unsigned singleCharOps = 0; singleCharOps <= 3; ++singleCharOps) + { + // Write less than buffer size, 1 or 2 buffers or even more, assuming buffer size of 3 + for(size_t bufSize : {0, 2, 3, 6, 7}) + { + if(singleCharOps + bufSize == 0u) + continue; + TEST(buf.open(filepath, flags)); + for(size_t i = 0; i < data.size();) + { + TEST_CONTEXT("sc:" << singleCharOps << " buf:" << bufSize << " i:" << i); + for(unsigned j = 0; j < singleCharOps && i < data.size(); ++j, ++i) + { + TEST_EQ(buf.sputc(data[i]), CharTraits::to_int_type(data[i])); + } + if(bufSize != 0u) + { + const auto remainSize = std::min(data.size() - i, bufSize); + TEST_EQ(buf.sputn(&data[i], remainSize), static_cast(remainSize)); + i += remainSize; + } + TEST_EQ(buf.pubsync(), 0); + TEST_EQ(read_file(filepath, binary ? data_type::binary : data_type::text), data.substr(0, i)); + } + TEST(buf.close()); + TEST_EQ(read_file(filepath, binary ? data_type::binary : data_type::text), data); + } + } +} + +void subtest_singlechar_positioning(const std::string& filepath, bool binary, const std::string& data) +{ + nw::filebuf buf; + // Use a small buffer to force filling it up w/o requiring to write lot's of data + char buffer[3]{}; + const auto mode = make_mode(std::ios_base::in | std::ios_base::out | std::ios_base::trunc, binary); + TEST(open_with_buffer(buf, filepath, mode, buffer)); + + // Put each char and record its position + std::vector pos(data.size()); + for(unsigned i = 0; i < data.size(); ++i) + { + buf.sputc(data[i]); + pos[i] = buf.pubseekoff(0, std::ios_base::cur); + } + // Go back to start and verify reading yields the same data and positions + buf.pubseekoff(0, std::ios_base::beg); + for(unsigned i = 0; i < data.size(); ++i) + { + TEST_CONTEXT("Position " << i); + TEST_EQ(buf.sbumpc(), CharTraits::to_int_type(data[i])); + TEST_EQ(buf.pubseekoff(0, std::ios_base::cur), pos[i]); + } +} + +void subtest_singlechar_multichar_reads(const std::string& filepath, bool binary, const std::string& data) +{ + create_file(filepath, data, binary ? data_type::binary : data_type::text); + nw::filebuf buf; + // Use a small buffer to force filling it up w/o requiring to write lot's of data + char buffer[3]{}; + TEST(open_with_buffer(buf, filepath, make_mode(std::ios_base::in, binary), buffer)); + + // Do a series of single-char and multi-char reads with varying size combinations + // Especially test the case of only single-char and only multi-char ops + for(unsigned singleCharOps = 0; singleCharOps <= 3; ++singleCharOps) + { + // Read less than buffer size, 1 or 2 buffers or even more, assuming buffer size of 3 + for(size_t bufSize : {0, 2, 3, 6, 7}) + { + if(singleCharOps + bufSize == 0u) + continue; + + std::string outBuf(bufSize, '\0'); + buf.pubseekoff(0, std::ios_base::beg); + for(size_t i = 0; i < data.size();) + { + TEST_CONTEXT("sc:" << singleCharOps << " buf:" << bufSize << " i:" << i); + for(unsigned j = 0; j < singleCharOps && i < data.size(); ++j, ++i) + { + TEST_EQ(buf.sbumpc(), CharTraits::to_int_type(data[i])); + } + if(bufSize == 0u) + continue; + const size_t readSize = std::min(data.size() - i, bufSize); + TEST_EQ(buf.sgetn(&outBuf.front(), bufSize), static_cast(readSize)); + if(readSize < bufSize) + outBuf.resize(readSize); + TEST_EQ(outBuf, data.substr(i, readSize)); + i += bufSize; + } + } + } +} + +void test_sungetc(const std::string& filepath, bool binary) +{ + const std::string data = "012345\n6"; + create_file(filepath, data, binary ? data_type::binary : data_type::text); + + // Use a small buffer to force filling it up w/o requiring to write lot's of data + char buffer[4]; + nw::filebuf buf; + TEST(open_with_buffer(buf, filepath, make_mode(std::ios_base::in, binary), buffer)); + + // Nothing to unget at beginning of file + TEST_EQ(buf.sungetc(), eof); + + // Able to unget first char and get it again + TEST_EQ(buf.sbumpc(), '0'); + TEST(buf.sungetc() != eof); + TEST_EQ(buf.sbumpc(), '0'); + + // Able to unget and reread after filling up new buffer + TEST(skip_chars(buf, sizeof(buffer) - 1u)); // skip remaining chars + TEST_EQ(buf.sbumpc(), '4'); + TEST(buf.sungetc() != eof); + // Ungetting multiple chars may or may not be possible + if(buf.sungetc() != eof) + TEST_EQ(buf.sbumpc(), '3'); + TEST_EQ(buf.sbumpc(), '4'); + + // \n also works + TEST_EQ(buf.sbumpc(), '5'); + TEST_EQ(buf.sbumpc(), '\n'); + TEST(buf.sungetc() != eof); + TEST_EQ(buf.sbumpc(), '\n'); + TEST_EQ(buf.sbumpc(), '6'); + TEST(buf.sungetc() != eof); + if(buf.sungetc() != eof) + TEST_EQ(buf.sbumpc(), '\n'); + TEST_EQ(buf.sbumpc(), '6'); + + // Go back as far as possible + auto idx = data.size(); + while(buf.sungetc() != eof) + { + TEST(idx > 0u); + --idx; + } + TEST(idx < data.size()); // At least 1 + // Get all put back chars + for(; idx < data.size(); ++idx) + TEST_EQ(buf.sbumpc(), data[idx]); +} + +void test_sputbackc(const std::string& filepath, bool binary) +{ + const std::string data = "012345\n6"; + create_file(filepath, data, binary ? data_type::binary : data_type::text); + + // Use a small buffer to force filling it up w/o requiring to write lot's of data + char buffer[4]; + nw::filebuf buf; + TEST(open_with_buffer(buf, filepath, make_mode(std::ios_base::in, binary), buffer)); + TEST(skip_chars(buf, data.size())); + + // Put back same chars explicitly (as many as possible) + auto idx = data.size(); + while(true) + { + auto res = buf.sputbackc((idx > 0u) ? data[idx - 1] : 'z'); + if(res == eof) + break; + TEST(idx > 0u); + TEST_EQ(res, data[idx - 1]); + --idx; + } + TEST(idx < data.size()); // At least 1 + // Get all put back chars + for(; idx < data.size(); ++idx) + TEST_EQ(buf.sbumpc(), data[idx]); + + // Put back different chars (as many as possible) + const std::string data2 = "789\nab\nc"; + TEST_EQ(data.size(), data2.size()); + idx = data2.size(); + while(true) + { + auto res = buf.sputbackc((idx > 0u) ? data2[idx - 1] : 'z'); + if(res == eof) + break; + TEST(idx > 0u); + TEST_EQ(res, data2[idx - 1]); + --idx; +#if !BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT + break; // Some stdlibs fail on putting back multiple different chars +#endif + } +#if BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT + // At least 1 when using custom filebuf + // But e.g. libc++ doesn't allow putting back a different char + TEST(idx < data2.size()); +#endif + // Get all put back chars + for(; idx < data2.size(); ++idx) + TEST_EQ(buf.sbumpc(), data2[idx]); +} + +void test_textmode(const std::string& filepath) +{ + // Test input, output and getting the file position works for text files with newlines + const std::string data = []() { + // Some simple test data + std::string result = "1234567890"; + // Line break after every char + result.reserve(result.size() + 27 * 2); + for(char c = 'a'; c <= 'z'; ++c) + (result += c) += '\n'; + // Some continuous line breaks + result.append(4, '\n'); + return result; + }(); + subtest_singlechar_positioning(filepath, false, data); + subtest_singlechar_multichar_reads(filepath, false, data); + subtest_sync(filepath, false, data); +} + +// Almost the same test as test_textmode but uses a binary stream. +// Useful as the buffer handling is very different +void test_binarymode(const std::string& filepath) +{ + const std::string data = "123" + create_random_data(65, data_type::binary); + subtest_singlechar_positioning(filepath, true, data); + subtest_singlechar_multichar_reads(filepath, true, data); + subtest_sync(filepath, true, data); +} + void test_swap(const std::string& filepath) { const std::string filepath2 = filepath + ".2"; remove_file_at_exit _(filepath); remove_file_at_exit _2(filepath2); - const auto eof = nw::filebuf::traits_type::eof(); // Note: Make sure to have en uneven number of swaps so the destructor runs on the others data // Check: FILE*, buffer, buffer_size @@ -301,15 +797,25 @@ void test_swap(const std::string& filepath) } } -// coverity [root_function] +// coverity[root_function] void test_main(int, char** argv, char**) { const std::string exampleFilename = std::string(argv[0]) + "-\xd7\xa9-\xd0\xbc-\xce\xbd.txt"; - test_open_close(exampleFilename); test_pubseekpos(exampleFilename); test_pubseekoff(exampleFilename); test_64_bit_seek(exampleFilename); + for(const auto binary : {false, true}) + { + std::cout << "Testing " << (binary ? "binary" : "text") << " mode\n"; + remove_file_at_exit _(exampleFilename); + test_xsgetn(exampleFilename, binary); + test_xsputn(exampleFilename, binary); + test_read_write_switch(exampleFilename, binary); + test_sungetc(exampleFilename, binary); + test_sputbackc(exampleFilename, binary); + binary ? test_binarymode(exampleFilename) : test_textmode(exampleFilename); + } // These tests are only useful for the nowide filebuf and are known to fail for // std::filebuf due to bugs in libc++ #if BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT diff --git a/test/test_fs.cpp b/test/test_fs.cpp index e22038c6..315f20f6 100644 --- a/test/test_fs.cpp +++ b/test/test_fs.cpp @@ -1,11 +1,9 @@ // -// Copyright (c) 2015 Artyom Beilis (Tonkikh) -// Copyright (c) 2021 Alexander Grund -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2015 Artyom Beilis (Tonkikh) +// Copyright (c) 2021 Alexander Grund // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt #if defined(__GNUC__) && __GNUC__ >= 7 #pragma GCC diagnostic ignored "-Wattributes" @@ -15,13 +13,104 @@ #include #include #include +#include #include "test.hpp" + +#include // Required for feature macro check below +// Conditional include to avoid warning/message +#if defined(__cpp_lib_quoted_string_io) && __cpp_lib_quoted_string_io >= 201304 +#include +#endif + +#include +#include #if defined(_MSC_VER) #pragma warning(disable : 4714) // function marked as __forceinline not inlined #endif -#include +#include + +// Exclude apple as support there is target level specific -.- +#if defined(__cpp_lib_filesystem) && !defined(__APPLE__) +#include +#define BOOST_NOWIDE_TEST_STD_PATH +#endif +#if defined(__cpp_lib_experimental_filesystem) +#ifndef _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING +#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING +#endif +#include +#define BOOST_NOWIDE_TEST_STD_EXPERIMENTAL_PATH +#endif + +template +struct is_istreamable : std::false_type +{}; +using boost::nowide::detail::void_t; +template +struct is_istreamable() >> std::declval())>> : std::true_type +{}; + +template +std::string maybe_narrow(const std::basic_string& s) +{ + return boost::nowide::narrow(s); +} + +const std::string& maybe_narrow(const std::string& s) +{ + return s; +} + +template +void test_fs_path_io(std::string utf8_name) +{ +#if defined(__cpp_lib_quoted_string_io) && __cpp_lib_quoted_string_io >= 201304 + Path path(boost::nowide::utf::convert_string(utf8_name)); + // Get native and UTF-8/narrow name here as the Path ctor may change the string (e.g. slash substitution) + const auto nativeName = path.native(); + utf8_name = maybe_narrow(nativeName); + // Output + std::ostringstream s, sRef; + sRef << std::quoted(utf8_name); + s << boost::nowide::quoted(path); + TEST_EQ(s.str(), sRef.str()); + // const + const Path constPath(path); + s.str(""); + s << boost::nowide::quoted(constPath); + TEST_EQ(s.str(), sRef.str()); + // Rvalue + s.str(""); + s << boost::nowide::quoted(Path(path)); + TEST_EQ(s.str(), sRef.str()); + + // Input + std::istringstream sIn(sRef.str()); + Path pathOut; + static_assert(is_istreamable::value, "!"); + sIn >> boost::nowide::quoted(pathOut); + TEST_EQ(pathOut.native(), nativeName); + // Can't read into a const path + static_assert(!is_istreamable::value, "!"); + // or an Rvalue + static_assert(!is_istreamable::value, "!"); + + // Wide stream + std::wostringstream ws, wsRef; + wsRef << std::quoted(boost::nowide::widen(utf8_name)); + ws << boost::nowide::quoted(path); + TEST_EQ(ws.str(), wsRef.str()); + std::wistringstream wsIn(wsRef.str()); + pathOut.clear(); + wsIn >> boost::nowide::quoted(pathOut); + TEST_EQ(maybe_narrow(pathOut.native()), utf8_name); +#else + (void)utf8_name; // Suppress unused warning + std::cout << "Skipping tests for boost::nowide::quoted" << std::endl; +#endif +} -// coverity [root_function] +// coverity[root_function] void test_main(int, char** argv, char**) { boost::nowide::nowide_filesystem(); @@ -65,4 +154,15 @@ void test_main(int, char** argv, char**) TEST(test == "Test"); } boost::filesystem::remove(path); + + std::cout << "Testing boost::filesystem::path" << std::endl; + test_fs_path_io(utf8_name); +#ifdef BOOST_NOWIDE_TEST_STD_EXPERIMENTAL_PATH + std::cout << "Testing std::experimental::filesystem::path" << std::endl; + test_fs_path_io(utf8_name); +#endif +#ifdef BOOST_NOWIDE_TEST_STD_PATH + std::cout << "Testing std::filesystem::path" << std::endl; + test_fs_path_io(utf8_name); +#endif } diff --git a/test/test_fstream.cpp b/test/test_fstream.cpp index 292c1deb..0c7257d0 100644 --- a/test/test_fstream.cpp +++ b/test/test_fstream.cpp @@ -1,9 +1,8 @@ -// Copyright (c) 2015 Artyom Beilis (Tonkikh) -// Copyright (c) 2019-2021 Alexander Grund +// Copyright (c) 2015 Artyom Beilis (Tonkikh) +// Copyright (c) 2019-2021 Alexander Grund // -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt #include @@ -533,7 +532,7 @@ void test_flush(const std::string& filepath) TEST(!fo.seekg(0)); // Does not work on closed stream } -// coverity [root_function] +// coverity[root_function] void test_main(int, char** argv, char**) { const std::string exampleFilename = std::string(argv[0]) + "-\xd7\xa9-\xd0\xbc-\xce\xbd.txt"; diff --git a/test/test_fstream_special.cpp b/test/test_fstream_special.cpp index 03e22dbd..37a5e9f4 100644 --- a/test/test_fstream_special.cpp +++ b/test/test_fstream_special.cpp @@ -1,9 +1,8 @@ -// Copyright (c) 2015 Artyom Beilis (Tonkikh) -// Copyright (c) 2019-2021 Alexander Grund +// Copyright (c) 2015 Artyom Beilis (Tonkikh) +// Copyright (c) 2019-2021 Alexander Grund // -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt #include @@ -29,24 +28,22 @@ void test_with_different_buffer_sizes(const char* filepath) */ for(int i = -1; i < 16; i++) { + remove_file_at_exit _(filepath); + std::cout << "Buffer size = " << i << std::endl; char buf[16]; nw::fstream f; // Different conditions when setbuf might be called: Usually before opening a file is OK if(i >= 0) - f.rdbuf()->pubsetbuf((i == 0) ? NULL : buf, i); + f.rdbuf()->pubsetbuf((i == 0) ? nullptr : buf, i); f.open(filepath, std::ios::in | std::ios::out | std::ios::trunc | std::ios::binary); TEST(f); - remove_file_at_exit _(filepath); // Add 'abcdefg' TEST(f.put('a')); TEST(f.put('b')); TEST(f.put('c')); - TEST(f.put('d')); - TEST(f.put('e')); - TEST(f.put('f')); - TEST(f.put('g')); + TEST(f.write("defg", 4)); // Read first char TEST(f.seekg(0)); TEST_EQ(f.get(), 'a'); @@ -86,18 +83,25 @@ void test_with_different_buffer_sizes(const char* filepath) TEST_EQ(f.get(), 'e'); // Putback after flush is implementation defined - // Boost.Nowide: Works -#if BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT TEST(f << std::flush); - TEST(f.putback('e')); - TEST(f.putback('d')); - TEST_EQ(f.get(), 'd'); - TEST_EQ(f.get(), 'e'); + if(f.putback('e')) + { + if(f.putback('d')) + TEST_EQ(f.get(), 'd'); + else + f.clear(); // LCOV_EXCL_LINE + TEST_EQ(f.get(), 'e'); + } else + f.clear(); TEST(f << std::flush); - TEST(f.unget()); - TEST_EQ(f.get(), 'e'); -#endif + if(f.unget()) + TEST_EQ(f.get(), 'e'); + else + f.clear(); + // Put back different char + TEST(f.seekg(-1, std::ios::cur)); + TEST_EQ(f.get(), 'e'); TEST(f.putback('x')); TEST_EQ(f.get(), 'x'); // Rest of sequence @@ -112,11 +116,15 @@ void test_with_different_buffer_sizes(const char* filepath) TEST(f.putback('B')); // Putting back multiple chars is not possible on all implementations after a seek/flush #if BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT - TEST(f.putback('a')); - TEST(!f.putback('x')); // At beginning of file -> No putback possible - // Get characters that were putback to avoid MSVC bug https://github.com/microsoft/STL/issues/342 - f.clear(); - TEST_EQ(f.get(), 'a'); + if(f.putback('a')) + { + // At beginning of file -> No putback possible + TEST(!f.putback('x')); // LCOV_EXCL_LINE + f.clear(); // LCOV_EXCL_LINE + // Get characters that were putback to avoid MSVC bug https://github.com/microsoft/STL/issues/342 + TEST_EQ(f.get(), 'a'); // LCOV_EXCL_LINE + } else + f.clear(); #endif TEST_EQ(f.get(), 'B'); f.close(); @@ -214,6 +222,7 @@ void test_swap(const char* filename, const char* filename2) { const int curChar1 = f1.peek(); const int curChar2 = f2.peek(); + TEST_CONTEXT("ctr " << ctr << ": c1=" << curChar1 << " c2=" << curChar2); // Randomly do a no-op seek of either or both streams to flush internal buffer if(ctr % 10 == 0) TEST(f1.seekg(f1.tellg())); @@ -226,9 +235,9 @@ void test_swap(const char* filename, const char* filename2) TEST(f1.seekg(f1.tellg())); else if(ctr % 15 == 4) TEST(f2.seekg(f2.tellg())); - TEST_EQ(f1.get(), char(curChar2)); + TEST_EQ(f1.get(), curChar2); f1.swap(f2); - TEST_EQ(f1.get(), char(curChar1)); + TEST_EQ(f1.get(), curChar1); ++ctr; } } @@ -257,7 +266,7 @@ void testPutback(const char* filename) } } -// coverity [root_function] +// coverity[root_function] void test_main(int, char** argv, char**) { const std::string exampleFilename = std::string(argv[0]) + "-\xd7\xa9-\xd0\xbc-\xce\xbd.txt"; diff --git a/test/test_ifstream.cpp b/test/test_ifstream.cpp index 82cf10a0..5d612c14 100644 --- a/test/test_ifstream.cpp +++ b/test/test_ifstream.cpp @@ -1,9 +1,8 @@ -// Copyright (c) 2015 Artyom Beilis (Tonkikh) -// Copyright (c) 2019-2021 Alexander Grund +// Copyright (c) 2015 Artyom Beilis (Tonkikh) +// Copyright (c) 2019-2021 Alexander Grund // -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt #include @@ -199,7 +198,7 @@ void test_move_and_swap(const std::string& filename) } } -// coverity [root_function] +// coverity[root_function] void test_main(int, char** argv, char**) { const std::string exampleFilename = std::string(argv[0]) + "-\xd7\xa9-\xd0\xbc-\xce\xbd.txt"; diff --git a/test/test_iostream.cpp b/test/test_iostream.cpp index 2b0e8233..ff7e4c60 100644 --- a/test/test_iostream.cpp +++ b/test/test_iostream.cpp @@ -1,9 +1,8 @@ -// Copyright (c) 2015 Artyom Beilis (Tonkikh) -// Copyright (c) 2020 - 2021 Alexander Grund +// Copyright (c) 2015 Artyom Beilis (Tonkikh) +// Copyright (c) 2020 - 2021 Alexander Grund // -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt #ifndef _SCL_SECURE_NO_WARNINGS // Call to 'std::copy_n' with parameters that may be unsafe @@ -36,6 +35,11 @@ const std::string outputString = const bool usesNowideRdBufIn = nw::cin.rdbuf() != std::cin.rdbuf(); const bool usesNowideRdBufOut = nw::cout.rdbuf() != std::cout.rdbuf(); +bool is_buffered(const std::ostream& os) +{ + return (os.flags() & std::ios_base::unitbuf) == 0; +} + #ifndef BOOST_NOWIDE_TEST_INTERACTIVE class mock_output_buffer final : public nw::detail::console_output_buffer_base { @@ -135,12 +139,14 @@ void test_is_valid_UTF8() TEST(!is_valid_UTF8(invalid_utf8_tests[0].utf8)); // Detect invalid } -void test_tie() +void test_tie_and_buffered() { TEST(nw::cin.tie() == &nw::cout); TEST(nw::cerr.tie() == &nw::cout); - TEST((nw::cerr.flags() & std::ios_base::unitbuf) != 0); TEST(nw::clog.tie() == nullptr); + TEST(is_buffered(nw::cout)); + TEST(!is_buffered(nw::cerr)); + TEST(is_buffered(nw::clog)); } void test_putback_and_get() @@ -234,6 +240,20 @@ void test_cerr() TEST_MOCKED(mock_buf.output == nw::widen("aHello World")); } +void test_clog() +{ + if(usesNowideRdBufOut) // Only executed when attached to a real terminal, i.e. not on CI + { + TEST(nw::clog.rdbuf() != std::clog.rdbuf()); // LCOV_EXCL_LINE + // for the std:: streams this is not true for all implementations, so only check when using custom buffers + TEST(nw::clog.rdbuf() != nw::cerr.rdbuf()); // LCOV_EXCL_LINE + } + + TEST(nw::clog.rdbuf() != nw::cin.rdbuf()); + TEST(nw::clog.rdbuf() != nw::cout.rdbuf()); + TEST(nw::clog.rdbuf() != std::cout.rdbuf()); +} + void test_cerr_single_char() { INSTALL_MOCK_BUF(cerr, mock_output_buffer); @@ -349,13 +369,16 @@ void test_ctrl_z_is_eof() TEST_MOCKED(value == "Reached after clear()"); #ifndef BOOST_NOWIDE_TEST_INTERACTIVE // CTRL+Z anywhere else but at the start of a line does not matter + nw::cout << "CTRL+Z Test:"; for(int i = 1; i <= 1100; i++) { + nw::cout << '.' << std::flush; // Progress indicator const std::string expected = create_random_one_line_string(i) + "\x1a"; mock_buf.inputs.push(std::wstring(expected.begin(), expected.end()) + L"\r\n"); TEST(std::getline(nw::cin, value)); TEST_EQ(value, expected); } + nw::cout << std::endl; #endif } @@ -378,7 +401,13 @@ class RedirectStdio { if(handleType == STD_INPUT_HANDLE) { - h = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); + h = CreateFile("CONIN$", + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, + OPEN_EXISTING, + 0, + 0); } else { h = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, @@ -389,7 +418,9 @@ class RedirectStdio } TEST(h != INVALID_HANDLE_VALUE); TEST(SetStdHandle(handleType, h)); - if(handleType != STD_INPUT_HANDLE) + if(handleType == STD_INPUT_HANDLE) + TEST(SetConsoleMode(h, ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | ENABLE_EXTENDED_FLAGS)); + else TEST(SetConsoleActiveScreenBuffer(h)); } ~RedirectStdio() @@ -405,7 +436,7 @@ class RedirectStdio CONSOLE_SCREEN_BUFFER_INFO info; TEST(GetConsoleScreenBufferInfo(h, &info)); TEST(info.dwSize.X > 0 && info.dwSize.Y > 0); - nw::cout << "Mock console buffer size: " << info.dwSize.X << "x" << info.dwSize.Y << "\n"; + std::cout << "Mock console buffer size: " << info.dwSize.X << "x" << info.dwSize.Y << "\n"; std::wstring result; std::vector buffer(info.dwSize.X); @@ -423,7 +454,7 @@ class RedirectStdio return result; } - void setBufferData(std::wstring data, int) + void setBufferData(const std::wstring& data) { std::vector buffer; buffer.reserve(data.size() * 2 + 2); @@ -457,25 +488,35 @@ class RedirectStdio void test_console() { - // cin +#ifndef BOOST_NOWIDE_DISABLE_CIN_TEST + std::cout << "Test cin console: " << std::flush; { RedirectStdio stdinHandle(STD_INPUT_HANDLE); + std::cout << "stdin redirected, " << std::flush; // Recreate to react on redirected streams decltype(nw::cin) cin(nullptr); + std::cout << "cin recreated " << std::flush; TEST(cin.rdbuf() != std::cin.rdbuf()); + std::cout << "and validated" << std::endl; const std::string testStringIn1 = "Hello std in "; const std::string testStringIn2 = "\xc3\xa4 - \xc3\xb6 - \xc3\xbc - \xd0\xbc - \xce\xbd"; - stdinHandle.setBufferData(nw::widen(testStringIn1 + "\n" + testStringIn2 + "\n"), 0); + std::cout << "Setting mock buffer data" << std::endl; + stdinHandle.setBufferData(nw::widen(testStringIn1 + "\n" + testStringIn2 + "\n")); + std::cout << "Done" << std::endl; std::string line; TEST(std::getline(cin, line)); + std::cout << "ASCII line read" << std::endl; TEST_EQ(line, testStringIn1); TEST(std::getline(cin, line)); + std::cout << "UTF-8 line read" << std::endl; TEST_EQ(line, testStringIn2); } - // cout +#endif + std::cout << "Test cout console" << std::endl; { RedirectStdio stdoutHandle(STD_OUTPUT_HANDLE); - decltype(nw::cout) cout(true, nullptr); + using cout_t = decltype(nw::cout); + cout_t cout(cout_t::target_stream::output, true, nullptr); TEST(cout.rdbuf() != std::cout.rdbuf()); const std::string testString = "Hello std out\n\xc3\xa4-\xc3\xb6-\xc3\xbc\n"; @@ -484,11 +525,12 @@ void test_console() const auto data = stdoutHandle.getBufferData(); TEST_EQ(data, nw::widen(testString)); } - // cerr + std::cout << "Test cerr console" << std::endl; { RedirectStdio stderrHandle(STD_ERROR_HANDLE); - decltype(nw::cerr) cerr(false, nullptr); + using cerr_t = decltype(nw::cerr); + cerr_t cerr(cerr_t::target_stream::error, false, nullptr); TEST(cerr.rdbuf() != std::cerr.rdbuf()); const std::string testString = "Hello std err\n\xc3\xa4-\xc3\xb6-\xc3\xbc\n"; @@ -497,6 +539,7 @@ void test_console() const auto data = stderrHandle.getBufferData(); TEST_EQ(data, nw::widen(testString)); } + std::cout << "Console tests done" << std::endl; } #else @@ -505,10 +548,9 @@ void test_console() #endif #endif -// coverity [root_function] +// coverity[root_function] void test_main(int argc, char** argv, char**) { - // LCOV_EXCL_START if(usesNowideRdBufIn) nw::cout << "Using Nowide input buffer\n"; else @@ -517,7 +559,6 @@ void test_main(int argc, char** argv, char**) nw::cout << "Using Nowide output buffer\n"; // LCOV_EXCL_LINE else nw::cout << "NOT using Nowide output buffer\n"; - // LCOV_EXCL_STOP const std::string arg = (argc == 1) ? "" : argv[1]; if(arg == "passthrough") // Read string from cin and write to cout @@ -553,12 +594,13 @@ void test_main(int argc, char** argv, char**) test_ctrl_z_is_eof(); // LCOV_EXCL_LINE #else test_is_valid_UTF8(); - test_tie(); + test_tie_and_buffered(); test_putback_and_get(); test_cout(); test_cout_single_char(); test_cerr(); test_cerr_single_char(); + test_clog(); test_cin(); test_cin_getline(); test_ctrl_z_is_eof(); diff --git a/test/test_iostream_passthrough.cmake b/test/test_iostream_passthrough.cmake index 8dfc2ad0..61396bfb 100644 --- a/test/test_iostream_passthrough.cmake +++ b/test/test_iostream_passthrough.cmake @@ -1,6 +1,6 @@ # Copyright 2019 - 2021 Alexander Grund # Distributed under the Boost Software License, Version 1.0. -# (See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt) +# https://www.boost.org/LICENSE_1_0.txt if(NOT DEFINED TEST_BINARY) if(CMAKE_ARGC GREATER 3) diff --git a/test/test_ofstream.cpp b/test/test_ofstream.cpp index 6d9f4c66..a2110960 100644 --- a/test/test_ofstream.cpp +++ b/test/test_ofstream.cpp @@ -1,9 +1,8 @@ -// Copyright (c) 2015 Artyom Beilis (Tonkikh) -// Copyright (c) 2019-2021 Alexander Grund +// Copyright (c) 2015 Artyom Beilis (Tonkikh) +// Copyright (c) 2019-2021 Alexander Grund // -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt #include @@ -170,7 +169,39 @@ void test_move_and_swap(const std::string& filename) TEST_EQ(read_file(filename2), "Foo Bar"); } -// coverity [root_function] +// Based on bug reported in #150 +void test_reopen(const std::string& filename) +{ + const std::string filename2 = filename + ".2"; + const std::string filename3 = filename + ".3"; + remove_file_at_exit _(filename); + remove_file_at_exit _2(filename2); + remove_file_at_exit _3(filename3); + + nw::ofstream f(filename, std::ios_base::binary); + using nw::test::data_type; + // Data sizes were randomly selected but above the usual default buffer size of 512 + const std::string testData = nw::test::create_random_data(613, data_type::binary); + TEST(f.write(testData.c_str(), testData.size())); + f.close(); + TEST_EQ(read_file(filename, data_type::binary), testData); + + // Reopen via open-function + f.open(filename2, std::ios_base::binary); + const std::string testData2 = nw::test::create_random_data(523, data_type::binary); + TEST(f.write(testData2.c_str(), testData2.size())); + f.close(); + TEST_EQ(read_file(filename2, data_type::binary), testData2); + + // Reopen via move-assign + f = nw::ofstream(filename3, std::ios_base::binary); + const std::string testData3 = nw::test::create_random_data(795, data_type::binary); + TEST(f.write(testData3.c_str(), testData3.size())); + f.close(); + TEST_EQ(read_file(filename3, data_type::binary), testData3); +} + +// coverity[root_function] void test_main(int, char** argv, char**) { const std::string exampleFilename = std::string(argv[0]) + "-\xd7\xa9-\xd0\xbc-\xce\xbd.txt"; @@ -180,4 +211,5 @@ void test_main(int, char** argv, char**) test_open(exampleFilename.c_str()); test_open(exampleFilename); test_move_and_swap(exampleFilename); + test_reopen(exampleFilename); } diff --git a/test/test_sets.hpp b/test/test_sets.hpp index d4e0c8a3..bd46ee42 100644 --- a/test/test_sets.hpp +++ b/test/test_sets.hpp @@ -1,10 +1,9 @@ // -// Copyright (c) 2012 Artyom Beilis (Tonkikh) -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2012 Artyom Beilis (Tonkikh) // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + #ifndef BOOST_NOWIDE_TEST_SETS_HPP_INCLUDED #define BOOST_NOWIDE_TEST_SETS_HPP_INCLUDED diff --git a/test/test_stackstring.cpp b/test/test_stackstring.cpp index c0cd882b..57b67249 100644 --- a/test/test_stackstring.cpp +++ b/test/test_stackstring.cpp @@ -1,11 +1,9 @@ // -// Copyright (c) 2012 Artyom Beilis (Tonkikh) -// Copyright (c) 2019-2020 Alexander Grund -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2012 Artyom Beilis (Tonkikh) +// Copyright (c) 2019-2020 Alexander Grund // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt #include #include "test.hpp" @@ -62,7 +60,7 @@ std::string heap_stackstring_to_narrow(const std::wstring& s) return ss.get(); } -// coverity [root_function] +// coverity[root_function] void test_main(int, char**, char**) { std::string hello = "\xd7\xa9\xd7\x9c\xd7\x95\xd7\x9d"; @@ -72,25 +70,25 @@ void test_main(int, char**, char**) { std::cout << "-- Default constructed string is NULL" << std::endl; const boost::nowide::short_stackstring s; - TEST(s.get() == NULL); + TEST(s.get() == nullptr); } { - std::cout << "-- NULL ptr passed to ctor results in NULL" << std::endl; - const boost::nowide::short_stackstring s(NULL); - TEST(s.get() == NULL); - const boost::nowide::short_stackstring s2(NULL, NULL); - TEST(s2.get() == NULL); + std::cout << "-- nullptr passed to ctor results in NULL" << std::endl; + const boost::nowide::short_stackstring s(nullptr); + TEST(s.get() == nullptr); + const boost::nowide::short_stackstring s2(nullptr, nullptr); + TEST(s2.get() == nullptr); } { - std::cout << "-- NULL ptr passed to convert results in NULL" << std::endl; + std::cout << "-- nullptr passed to convert results in NULL" << std::endl; boost::nowide::short_stackstring s(L"foo"); TEST(s.get() == std::string("foo")); - s.convert(NULL); - TEST(s.get() == NULL); + s.convert(nullptr); + TEST(s.get() == nullptr); boost::nowide::short_stackstring s2(L"foo"); TEST(s2.get() == std::string("foo")); - s2.convert(NULL, NULL); - TEST(s2.get() == NULL); + s2.convert(nullptr, nullptr); + TEST(s2.get() == nullptr); } { std::cout << "-- An empty string is accepted" << std::endl; @@ -168,7 +166,7 @@ void test_main(int, char**, char**) TEST(sw3.get() == heapVal); // Assign empty sw3 = sEmpty; //-V820 - TEST(sw3.get() == NULL); + TEST(sw3.get() == nullptr); } { stackstring sw2(stack), sw3, sEmpty; @@ -180,7 +178,7 @@ void test_main(int, char**, char**) TEST(sw3.get() == stackVal); // Assign empty sw3 = sEmpty; //-V820 - TEST(sw3.get() == NULL); + TEST(sw3.get() == nullptr); } { stackstring sw2(stack); @@ -202,10 +200,10 @@ void test_main(int, char**, char**) TEST(sw3.get() == stackVal); swap(sw2, sEmpty1); TEST(sEmpty1.get() == heapVal); - TEST(sw2.get() == NULL); + TEST(sw2.get() == nullptr); swap(sw3, sEmpty2); TEST(sEmpty2.get() == stackVal); - TEST(sw3.get() == NULL); + TEST(sw3.get() == nullptr); } { stackstring sw2(heap), sw3(heap); diff --git a/test/test_stat.cpp b/test/test_stat.cpp index 18430336..13c1082a 100644 --- a/test/test_stat.cpp +++ b/test/test_stat.cpp @@ -1,9 +1,8 @@ // -// Copyright (c) 2020 Alexander Grund -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2020 Alexander Grund // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt #include @@ -13,7 +12,7 @@ #include #endif -// coverity [root_function] +// coverity[root_function] void test_main(int, char** argv, char**) { const std::string prefix = argv[0]; @@ -62,6 +61,10 @@ void test_main(int, char** argv, char**) // Need to use the detail function directly TEST_EQ(boost::nowide::detail::stat(filename.c_str(), &stdStat, sizeof(stdStat) - 4u), EINVAL); TEST_EQ(errno, EINVAL); + // Same for our stat_t + boost::nowide::stat_t boostStat; + TEST_EQ(boost::nowide::detail::stat(filename.c_str(), &boostStat, sizeof(boostStat) - 4u), EINVAL); + TEST_EQ(errno, EINVAL); } #endif diff --git a/test/test_stdio.cpp b/test/test_stdio.cpp index 95c35d57..2d40b3c6 100644 --- a/test/test_stdio.cpp +++ b/test/test_stdio.cpp @@ -1,11 +1,9 @@ // -// Copyright (c) 2012 Artyom Beilis (Tonkikh) -// Copyright (c) 2019 Alexander Grund -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2012 Artyom Beilis (Tonkikh) +// Copyright (c) 2019 Alexander Grund // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt #include @@ -43,18 +41,18 @@ void create_test_file(const std::string& filename) std::fclose(f); } -#if BOOST_MSVC +#ifdef BOOST_MSVC #include // For _CrtSetReportMode void noop_invalid_param_handler(const wchar_t*, const wchar_t*, const wchar_t*, unsigned, uintptr_t) {} // LCOV_EXCL_LINE #endif -// coverity [root_function] +// coverity[root_function] void test_main(int, char** argv, char**) { const std::string prefix = argv[0]; const std::string filename = prefix + "\xd7\xa9-\xd0\xbc-\xce\xbd.txt"; -#if BOOST_MSVC +#ifdef BOOST_MSVC // Prevent abort on freopen(NULL, ...) _set_invalid_parameter_handler(noop_invalid_param_handler); #endif @@ -80,7 +78,7 @@ void test_main(int, char** argv, char**) { boost::nowide::remove(filename.c_str()); TEST(!file_exists(filename)); - TEST(boost::nowide::fopen(filename.c_str(), "r") == NULL); + TEST(boost::nowide::fopen(filename.c_str(), "r") == nullptr); TEST(!file_exists(filename)); } std::cout << " -- freopen" << std::endl; @@ -99,7 +97,7 @@ void test_main(int, char** argv, char**) // Reopen in read mode // Note that changing the mode is not possibly on all implementations // E.g. MSVC disallows NULL completely as the file parameter - FILE* f2 = boost::nowide::freopen(NULL, "r", f); + FILE* f2 = boost::nowide::freopen(nullptr, "r", f); if(!f2) f2 = boost::nowide::freopen(filename.c_str(), "r", f); std::cout << " -- no write possible" << std::endl; diff --git a/test/test_system.cpp b/test/test_system.cpp index e003f0b1..76171375 100644 --- a/test/test_system.cpp +++ b/test/test_system.cpp @@ -1,17 +1,17 @@ // -// Copyright (c) 2012 Artyom Beilis (Tonkikh) -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2012 Artyom Beilis (Tonkikh) // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + #ifdef _MSC_VER +#ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif - -#include +#endif #include +#include #include #include #include "test.hpp" @@ -96,12 +96,12 @@ void run_child(int argc, char** argv, char** env) // Test arguments TEST_EQ(argc, 2); TEST_EQ(argv[1], example); - TEST(argv[2] == NULL); + TEST(argv[2] == nullptr); // Test getenv TEST(boost::nowide::getenv("BOOST_NOWIDE_TEST")); TEST_EQ(boost::nowide::getenv("BOOST_NOWIDE_TEST"), example); - TEST(boost::nowide::getenv("BOOST_NOWIDE_TEST_NONE") == NULL); + TEST(!boost::nowide::getenv("BOOST_NOWIDE_TEST_NONE")); // Empty variables are unreliable on windows, hence skip. E.g. using "set FOO=" unsets FOO #ifndef BOOST_WINDOWS TEST(boost::nowide::getenv("BOOST_NOWIDE_EMPTY")); @@ -121,10 +121,10 @@ void run_child(int argc, char** argv, char** env) std::cout << "Subprocess ok" << std::endl; } -void run_parent(const char* exe_path) +void run_parent(const std::string& exe_path) { TEST(boost::nowide::system(nullptr) != 0); - const std::string command = "\"" + std::string(exe_path) + "\" " + example; + const std::string command = "\"" + exe_path + "\" " + example; #if BOOST_NOWIDE_TEST_USE_NARROW TEST_EQ(boost::nowide::setenv("BOOST_NOWIDE_TEST", example.c_str(), 1), 0); TEST_EQ(boost::nowide::setenv("BOOST_NOWIDE_TEST_NONE", example.c_str(), 1), 0); @@ -142,7 +142,7 @@ void run_parent(const char* exe_path) #endif } -// coverity [root_function] +// coverity[root_function] void test_main(int argc, char** argv, char** env) { const int old_argc = argc; diff --git a/test/test_traits.cpp b/test/test_traits.cpp index b2e0a2e3..791d266b 100644 --- a/test/test_traits.cpp +++ b/test/test_traits.cpp @@ -1,13 +1,12 @@ // -// Copyright (c) 2020 Alexander Grund -// -// Distributed under the Boost Software License, Version 1.0. (See -// accompanying file LICENSE or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// Copyright (c) 2020 Alexander Grund // +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt #include #include +#include #include "test.hpp" #include #include @@ -26,7 +25,14 @@ // Exclude apple as support there is target level specific -.- #if defined(__cpp_lib_filesystem) && !defined(__APPLE__) #include -#define BOOST_NOWIDE_TEST_SFS_PATH +#define BOOST_NOWIDE_TEST_STD_PATH +#endif +#if defined(__cpp_lib_experimental_filesystem) +#ifndef _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING +#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING +#endif +#include +#define BOOST_NOWIDE_TEST_STD_EXPERIMENTAL_PATH #endif #ifdef BOOST_NOWIDE_TEST_BFS_PATH @@ -39,6 +45,17 @@ #include #endif +template +struct has_open : std::false_type +{}; +using boost::nowide::detail::void_t; +template +struct has_open().open(std::declval(), std::ios_base::openmode{}))>> + : std::true_type +{}; + using boost::nowide::detail::is_string_container; static_assert(is_string_container::value, "!"); static_assert(is_string_container::value, "!"); @@ -53,7 +70,7 @@ static_assert(get_data_width::value == sizeof(wchar_t), "!"); static_assert(get_data_width::value == sizeof(char16_t), "!"); static_assert(get_data_width::value == sizeof(char32_t), "!"); -// coverity [root_function] +// coverity[root_function] void test_main(int, char**, char**) { #ifdef BOOST_NOWIDE_TEST_STD_STRINGVIEW @@ -63,12 +80,67 @@ void test_main(int, char**, char**) static_assert(is_string_container::value, "!"); static_assert(is_string_container::value, "!"); #endif -#ifdef BOOST_NOWIDE_TEST_SFS_PATH +#ifdef BOOST_NOWIDE_TEST_STD_PATH std::cout << "Testing std::filesystem::path" << std::endl; - static_assert(boost::nowide::detail::is_path::value, "!"); + using fs_path = std::filesystem::path; + static_assert(boost::nowide::detail::is_path::value, "!"); +#if BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT + static_assert(has_open::value, "!"); +#endif + static_assert(has_open::value, "!"); + static_assert(has_open::value, "!"); + static_assert(has_open::value, "!"); + static_assert(std::is_constructible::value, "!"); + static_assert(std::is_constructible::value, "!"); + static_assert(std::is_constructible::value, "!"); + static_assert(has_open::value, "!"); + static_assert(has_open::value, "!"); + static_assert(has_open::value, "!"); + static_assert(has_open::value, "!"); + static_assert(std::is_constructible::value, "!"); + static_assert(std::is_constructible::value, "!"); + static_assert(std::is_constructible::value, "!"); +#endif +#ifdef BOOST_NOWIDE_TEST_STD_EXPERIMENTAL_PATH + std::cout << "Testing std::experimental::filesystem::path" << std::endl; + using exfs_path = std::experimental::filesystem::path; + static_assert(boost::nowide::detail::is_path::value, "!"); +#if BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT + static_assert(has_open::value, "!"); +#endif + static_assert(has_open::value, "!"); + static_assert(has_open::value, "!"); + static_assert(has_open::value, "!"); + static_assert(std::is_constructible::value, "!"); + static_assert(std::is_constructible::value, "!"); + static_assert(std::is_constructible::value, "!"); + static_assert(has_open::value, "!"); + static_assert(has_open::value, "!"); + static_assert(has_open::value, "!"); + static_assert(has_open::value, "!"); + static_assert(std::is_constructible::value, "!"); + static_assert(std::is_constructible::value, "!"); + static_assert(std::is_constructible::value, "!"); #endif #ifdef BOOST_NOWIDE_TEST_BFS_PATH std::cout << "Testing boost::filesystem::path" << std::endl; - static_assert(boost::nowide::detail::is_path::value, "!"); + using bfs_path = boost::filesystem::path; + static_assert(boost::nowide::detail::is_path::value, "!"); +#if BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT + static_assert(has_open::value, "!"); +#endif + static_assert(has_open::value, "!"); + static_assert(has_open::value, "!"); + static_assert(has_open::value, "!"); + static_assert(std::is_constructible::value, "!"); + static_assert(std::is_constructible::value, "!"); + static_assert(std::is_constructible::value, "!"); + static_assert(has_open::value, "!"); + static_assert(has_open::value, "!"); + static_assert(has_open::value, "!"); + static_assert(has_open::value, "!"); + static_assert(std::is_constructible::value, "!"); + static_assert(std::is_constructible::value, "!"); + static_assert(std::is_constructible::value, "!"); #endif } diff --git a/tools/create_standalone.sh b/tools/create_standalone.sh index aa218780..09cdd14a 100644 --- a/tools/create_standalone.sh +++ b/tools/create_standalone.sh @@ -2,7 +2,7 @@ # Copyright 2019 - 2020 Alexander Grund # Distributed under the Boost Software License, Version 1.0. -# (See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt) +# https://www.boost.org/LICENSE_1_0.txt set -euo pipefail @@ -29,10 +29,26 @@ mkdir -p "$targetFolder"/include cp -r include/boost/nowide "$targetFolder"/include cp -r config src test cmake CMakeLists.txt LICENSE README.md "$targetFolder" -cp standalone/*.hpp "$targetFolder"/include/nowide mv "$targetFolder/cmake/BoostAddWarnings.cmake" "$targetFolder/cmake/NowideAddWarnings.cmake" find "$targetFolder" -name 'Jamfile*' -delete +# Stitch config header together +# Remove the boost headers, the important parts of config.hpp will be put in later +config_hpp="$targetFolder/include/nowide/config.hpp" +sed -E '//d' -i "$config_hpp" +# Put config replacement header below the doxygen marker +lineTarget=$(grep -m 1 -n '//! @cond Doxygen_Suppress' "$config_hpp" | cut -d ":" -f 1) +lineSrc=$(grep -n 'boost/config.hpp' "standalone/config.hpp" | cut -d ":" -f 1) # Skip the file header +{ head -n $lineTarget "$config_hpp"; tail -n +$((lineSrc+2)) standalone/config.hpp; tail -n +$((lineTarget+1)) "$config_hpp"; } > "$config_hpp".new +mv "$config_hpp".new "$config_hpp" +sed -e '/Automatically link/,/auto-linking disabled/d' \ + -e 's/defined(BOOST_ALL_DYN_LINK) || //' \ + -e '/namespace boost/d' \ + -e 's/ BOOST_FALLTHROUGH//' \ + -i "$config_hpp" +sed -E 's/BOOST_VERSION . [0-9]+ && //g' -i "$config_hpp" # Checks against BOOST_VERSION + + SOURCES=$(find "$targetFolder" -name '*.hpp' -or -name '*.cpp') SOURCES_NO_BOOST=$(echo "$SOURCES" | grep -v 'filesystem.hpp') @@ -63,6 +79,7 @@ sed '/^if(NOT BOOST_SUPERPROJECT_SOURCE_DIR)/,/^endif/{/^if/d;/^endif/d}' -i "$t sed 's/NAMESPACE Nowide CONFIG_FILE.*$/NAMESPACE nowide)/' -i "$targetFolder/CMakeLists.txt" sed '/^if(NOT BOOST_SUPERPROJECT_SOURCE_DIR)/,/^endif/d' -i "$targetFolder/test/CMakeLists.txt" +sed 's/ DEFINITIONS NOWIDE_TEST_BFS_PATH.*)/)/' -i "$targetFolder/test/CMakeLists.txt" sed '/Nowide::filesystem/d' -i "$targetFolder/test/CMakeLists.txt" sed '/^# Those 2 should work the same/,/^elseif/d' -i "$targetFolder/test/cmake_test/CMakeLists.txt"