pax_global_header00006660000000000000000000000064147735714250014531gustar00rootroot0000000000000052 comment=ef1231ae490465b4f592ca326eee6d56a3d802ae qcoro-0.12.0/000077500000000000000000000000001477357142500127345ustar00rootroot00000000000000qcoro-0.12.0/.clang-format000066400000000000000000000102251477357142500153070ustar00rootroot00000000000000--- Language: Cpp # BasedOnStyle: LLVM AccessModifierOffset: -4 AlignAfterOpenBracket: Align AlignConsecutiveMacros: false AlignConsecutiveAssignments: false AlignConsecutiveBitFields: false AlignConsecutiveDeclarations: false AlignEscapedNewlines: Right AlignOperands: Align AlignTrailingComments: true AllowAllArgumentsOnNextLine: true AllowAllConstructorInitializersOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortEnumsOnASingleLine: false AllowShortBlocksOnASingleLine: Never AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: Empty AllowShortLambdasOnASingleLine: All AllowShortIfStatementsOnASingleLine: Never AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: Yes BinPackArguments: true BinPackParameters: true BraceWrapping: AfterCaseLabel: false AfterClass: false AfterControlStatement: Never AfterEnum: false AfterFunction: false AfterNamespace: false AfterObjCDeclaration: false AfterStruct: false AfterUnion: false AfterExternBlock: false BeforeCatch: false BeforeElse: false BeforeLambdaBody: false BeforeWhile: false IndentBraces: false SplitEmptyFunction: true SplitEmptyRecord: true SplitEmptyNamespace: true BreakBeforeBinaryOperators: None BreakBeforeBraces: Attach BreakBeforeInheritanceComma: false BreakInheritanceList: BeforeColon BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: BeforeColon BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true ColumnLimit: 100 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: true ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DeriveLineEnding: true DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true ForEachMacros: - foreach - Q_FOREACH - BOOST_FOREACH IncludeBlocks: Preserve IncludeCategories: - Regex: '^"(qcoro)/' Priority: 2 SortPriority: 0 - Regex: '^(<|"(gtest|gmock|isl|json)/)' Priority: 3 SortPriority: 0 - Regex: '.*' Priority: 1 SortPriority: 0 IncludeIsMainRegex: '(Test)?$' IncludeIsMainSourceRegex: '' IndentCaseLabels: false IndentCaseBlocks: false IndentGotoLabels: true IndentPPDirectives: None IndentExternBlock: AfterExternBlock IndentWidth: 4 IndentWrappedFunctionNames: false InsertTrailingCommas: None JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: true MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 NamespaceIndentation: None ObjCBinPackProtocolList: Auto ObjCBlockIndentWidth: 2 ObjCBreakBeforeNestedBlockParam: true ObjCSpaceAfterProperty: false ObjCSpaceBeforeProtocolList: true PenaltyBreakAssignment: 2 PenaltyBreakBeforeFirstCallParameter: 19 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyBreakTemplateDeclaration: 10 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 60 PointerAlignment: Right ReflowComments: false SortIncludes: true SortUsingDeclarations: true SpaceAfterCStyleCast: false SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: false SpaceBeforeAssignmentOperators: true SpaceBeforeCpp11BracedList: false SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatements SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyBlock: false SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInConditionalStatement: false SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false SpaceBeforeSquareBrackets: false Standard: Latest StatementMacros: - Q_UNUSED - QT_REQUIRE_VERSION - addTest TabWidth: 8 UseCRLF: false UseTab: Never WhitespaceSensitiveMacros: - STRINGIZE - PP_STRINGIZE - BOOST_PP_STRINGIZE ... qcoro-0.12.0/.git-blame-ignore-revs000066400000000000000000000001071477357142500170320ustar00rootroot00000000000000# Initial run of clang-format 0c01ca40fd8bd337ecae3ea21be1e5a97ba40e4b qcoro-0.12.0/.github/000077500000000000000000000000001477357142500142745ustar00rootroot00000000000000qcoro-0.12.0/.github/FUNDING.yml000066400000000000000000000001741477357142500161130ustar00rootroot00000000000000# Thanks for any donations! :) github: [danvratil] liberapay: dvratil ko_fi: dvratil custom: ['https://paypal.me/dvratil'] qcoro-0.12.0/.github/actions/000077500000000000000000000000001477357142500157345ustar00rootroot00000000000000qcoro-0.12.0/.github/actions/install-compiler/000077500000000000000000000000001477357142500212125ustar00rootroot00000000000000qcoro-0.12.0/.github/actions/install-compiler/action.yml000066400000000000000000000020741477357142500232150ustar00rootroot00000000000000name: Install Compiler description: Installs compiler inputs: compiler: description: Compiler to install (gcc|clang) required: true version: description: Version of compiler to install required: true runs: using: composite steps: - name: Install GCC if: ${{ inputs.compiler == 'gcc' }} shell: bash run: | GCC_VERSION_MAJOR=$(echo ${{ inputs.version }} | cut -d '.' -f1) sudo add-apt-repository "deb https://ppa.launchpadcontent.net/ubuntu-toolchain-r/ppa/ubuntu focal main" sudo apt-get update sudo apt-get install -y \ gcc-${GCC_VERSION_MAJOR} g++-${GCC_VERSION_MAJOR} libstdc++-${GCC_VERSION_MAJOR}-dev echo "CXX=/usr/bin/g++-${GCC_VERSION_MAJOR}" >> $GITHUB_ENV - name: Install Clang if : ${{ inputs.compiler == 'clang' }} shell: bash run: | wget https://apt.llvm.org/llvm.sh -O llvm.sh chmod a+x llvm.sh sed -i "s/libunwind-\$LLVM_VERSION-dev//" llvm.sh sudo ./llvm.sh ${{ inputs.version }} all echo "CXX=/usr/bin/clang++-${{ inputs.version }}" >> $GITHUB_ENVqcoro-0.12.0/.github/actions/install-qt/000077500000000000000000000000001477357142500200245ustar00rootroot00000000000000qcoro-0.12.0/.github/actions/install-qt/action.yml000066400000000000000000000047131477357142500220310ustar00rootroot00000000000000name: 'Install Qt' description: 'Install Qt' inputs: qt_version: description: 'Version of Qt to install' required: true qt_archives: description: 'List of Qt archives to intall' qt_modules: description: 'List of Qt modules to install' platform: description: 'Operating system (linux|windows|macos)' required: true runs: using: composite steps: - name: Install dependencies if: ${{ inputs.platform == 'linux' }} shell: bash run: | sudo apt-get update sudo apt-get install -y --no-install-recommends \ python3 python3-pip \ build-essential \ dbus dbus-x11 \ libgl-dev libegl-dev - name: Install Qt shell: bash run: | pip3 install aqtinstall~=2.1 case "${{ inputs.platform }}" in "windows") QT_ARCH="win64_msvc2019_64" QT_INSTALL_PATH="C:\\Qt" QT_BASE_DIR="${QT_INSTALL_PATH}\\${{ inputs.qt_version }}\\msvc2019_64" QT_OS="windows" ;; "linux") QT_ARCH="gcc_64" QT_INSTALL_PATH="/opt/qt" QT_BASE_DIR="${QT_INSTALL_PATH}/${{ inputs.qt_version }}/${QT_ARCH}" QT_OS="linux" ;; "macos") QT_ARCH="" QT_INSTALL_PATH="/Users/runner/qt" if [[ "$(echo ${{ inputs.qt_version }} | cut -d'.' -f1)" == "6" ]]; then QT_BASE_DIR="${QT_INSTALL_PATH}/${{ inputs.qt_version }}/macos" else QT_BASE_DIR="${QT_INSTALL_PATH}/${{ inputs.qt_version }}/clang_64" fi QT_OS="mac" ;; esac if [[ -n "${{ inputs.qt_archives }}" ]]; then qt_archives="--archives ${{ inputs.qt_archives }}" fi if [[ -n "${{ inputs.qt_modules }}" ]]; then qt_modules="--modules ${{ inputs.qt_modules }}" fi python3 -m aqt install-qt -O ${QT_INSTALL_PATH} ${QT_OS} desktop ${{ inputs.qt_version }} ${QT_ARCH} ${qt_archives} ${qt_modules} echo "${QT_BASE_DIR}/bin" >> $GITHUB_PATH echo "CMAKE_PREFIX_PATH=${QT_BASE_DIR}/lib/cmake" >> $GITHUB_ENV echo "LD_LIBRARY_PATH=${QT_BASE_DIR}/lib:${LD_LIBRARY_PATH}" >> $GITHUB_ENV echo "XDG_DATA_DIRS=${QT_BASE_DIR}/share:${XDG_DATA_DIRS}" >> $GITHUB_ENV if [[ "${{ inputs.platform }}" == "windows" ]]; then powershell "./.github/actions/install-qt/install-dbus.ps1" "$QT_BASE_DIR" fi qcoro-0.12.0/.github/actions/install-qt/install-dbus.ps1000066400000000000000000000011021477357142500230440ustar00rootroot00000000000000[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force Set-PSRepository -Name 'PSGallery' -SourceLocation "https://www.powershellgallery.com/api/v2" -InstallationPolicy Trusted Install-Module -Name 7Zip4PowerShell -Force Invoke-WebRequest -Uri https://files.kde.org/craft/master/23.09/windows/cl/msvc2019/x86_64/RelWithDebInfo/libs/dbus/dbus-1.14.8-20230912T142911-windows-cl-msvc2019-x86_64.7z -OutFile C:\Qt\dbus.7z Expand-7Zip -ArchiveFileName C:\Qt\dbus.7z -TargetPath $args[0] qcoro-0.12.0/.github/dependabot.yml000066400000000000000000000003531477357142500171250ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "pip" directory: "/" # Location of package manifests schedule: interval: "daily" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" qcoro-0.12.0/.github/workflows/000077500000000000000000000000001477357142500163315ustar00rootroot00000000000000qcoro-0.12.0/.github/workflows/build-docker-images.yml000066400000000000000000000041111477357142500226600ustar00rootroot00000000000000name: Build Docker images on: push: branches: - main paths: - 'docker/**' pull_request: types: [opened, synchronize, reopened, edited] paths: - 'docker/**' workflow_dispatch: env: BUILD_TYPE: RelWithDebInfo QTEST_FUNCTION_TIMEOUT: 60000 jobs: generate-matrix: name: Generate build matrix runs-on: ubuntu-latest outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} steps: - name: Checkout sources uses: actions/checkout@v4 - id: set-matrix name: Generate matrix run: | matrix_json=$(python3 ./.github/workflows/generate-matrix.py --platform linux) echo "::set-output name=matrix::${matrix_json}" build: needs: generate-matrix strategy: matrix: ${{ fromJSON(needs.generate-matrix.outputs.matrix) }} fail-fast: false runs-on: ${{ matrix.runs_on }} name: Build Docker image for ${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }} steps: - name: Checkout sources uses: actions/checkout@v4 - name: Docker Buildx setup uses: docker/setup-buildx-action@v3 - name: Login to GCR uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@v5 with: images: ghcr.io/${{ github.repository }}/build-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }} - name: Build and push image uses: docker/build-push-action@v6 with: build-args: | compiler_image=${{ matrix.compiler_base_image }} compiler_version=${{ matrix.compiler_version }} qt_version=${{ matrix.qt_version }} qt_modules=${{ matrix.qt_modules }} qt_archives=${{ matrix.qt_archives }} push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} file: docker/Dockerfile.${{ matrix.compiler }} context: docker qcoro-0.12.0/.github/workflows/build-linux.yml000066400000000000000000000105461477357142500213160ustar00rootroot00000000000000name: Linux CI on: push: branches: - main pull_request: types: [opened, synchronize, reopened, edited] env: BUILD_TYPE: Debug QTEST_FUNCTION_TIMEOUT: 60000 jobs: detect_run: name: Check changes outputs: { "source_code_changed": "${{ steps.source_code_changed.outputs.source_code_changed}}" } runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v4 - id: changed_files uses: tj-actions/changed-files@v46 name: Get changed files with: files_ignore: | docs/** docker/** requirements.txt mkdocs.yml README.md .github/** - id: source_code_changed name: Check for source code changes if: steps.changed_files.outputs.any_changed == 'true' run: | echo "Detected changed files:" echo "${{ steps.changed_files.outputs.all_changed_files }}" echo "::set-output name=source_code_changed::true" generate-matrix: name: Generate build matrix runs-on: ubuntu-latest outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} steps: - name: Checkout sources uses: actions/checkout@v4 - id: set-matrix name: Generate matrix run: | matrix_json=$(python3 ./.github/workflows/generate-matrix.py --platform=linux) echo "::set-output name=matrix::${matrix_json}" build: needs: [ generate-matrix, detect_run ] strategy: matrix: ${{ fromJSON(needs.generate-matrix.outputs.matrix) }} fail-fast: false defaults: run: shell: bash -l {0} runs-on: ${{ matrix.runs_on }} name: ${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }} container: image: ghcr.io/${{ github.repository }}/build-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }}:main steps: - name: Checkout sources uses: actions/checkout@v4 if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }} - name: Create Build Environment if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }} run: | cmake -E make_directory build - name: Configure CMake if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }} run: | QT_VERSION_MAJOR=$(echo ${{ matrix.qt_version }} | cut -d'.' -f1) EXTRA_CMAKE_FLAGS="${EXTRA_CMAKE_FLAGS} -DBUILD_SHARED_LIBS=ON" # Disable ASAN for clang-11 - we are hitting some bugs in ASAN & generators if [[ "${{ matrix.compiler }}" == "clang" && "${{ matrix.compiler_version }}" == "11" ]]; then EXTRA_CMAKE_FLAGS="${EXTRA_CMAKE_FLAGS} -DQCORO_ENABLE_ASAN=OFF" fi cmake -B build \ -DCMAKE_BUILD_TYPE=$BUILD_TYPE \ -DUSE_QT_VERSION=$QT_VERSION_MAJOR \ -DQCORO_WITH_QTDBUS=${{ matrix.with_qtdbus }} \ -DQCORO_ENABLE_ASAN=ON \ ${EXTRA_CMAKE_FLAGS} - name: Build if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }} run: | cmake --build build --config $BUILD_TYPE --parallel $(nproc) --verbose - name: Test if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }} run: | cd build QT_LOGGING_TO_CONSOLE=1 ctest -C $BUILD_TYPE \ --output-on-failure \ --verbose \ --output-junit ${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }}.xml - name: Upload Test Results if: ${{ needs.detect_run.outputs.source_code_changed == 'true' && always() }} uses: actions/upload-artifact@v4 with: name: Unit Tests Results (${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }}) path: | ${{ github.workspace }}/build/${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }}.xml - name: Upload build logs on failure if: ${{ needs.detect_run.outputs.source_code_changed == 'true' && failure() }} uses: actions/upload-artifact@v4 with: name: build-${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }} path: build/** event_file: name: "Event File" runs-on: ubuntu-latest steps: - name: Upload uses: actions/upload-artifact@v4 with: name: Event File path: ${{ github.event_path }} qcoro-0.12.0/.github/workflows/build-macos.yml000066400000000000000000000110311477357142500212470ustar00rootroot00000000000000name: MacOS CI on: push: branches: - main pull_request: types: [opened, synchronize, reopened, edited] env: BUILD_TYPE: Debug QTEST_FUNCTION_TIMEOUT: 60000 jobs: detect_run: name: Check changes outputs: { "source_code_changed": "${{ steps.source_code_changed.outputs.source_code_changed}}" } runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v4 - id: changed_files uses: tj-actions/changed-files@v46 name: Get changed files with: files_ignore: | docs/** docker/** requirements.txt mkdocs.yml README.md .github/** - id: source_code_changed name: Check for source code changes if: steps.changed_files.outputs.any_changed == 'true' run: | echo "Detected changed files:" echo "${{ steps.changed_files.outputs.all_changed_files }}" echo "::set-output name=source_code_changed::true" generate-matrix: name: Generate build matrix runs-on: ubuntu-latest outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} steps: - name: Checkout sources uses: actions/checkout@v4 - id: set-matrix name: Generate matrix run: | matrix_json=$(python3 ./.github/workflows/generate-matrix.py --platform=macos) echo "::set-output name=matrix::${matrix_json}" build: needs: [ generate-matrix, detect_run ] strategy: matrix: ${{ fromJSON(needs.generate-matrix.outputs.matrix) }} fail-fast: false runs-on: ${{ matrix.runs_on }} name: ${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }} steps: - name: Checkout sources uses: actions/checkout@v4 if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }} - name: Install Qt if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }} uses: ./.github/actions/install-qt with: qt_version: ${{ matrix.qt_version }} qt_archives: ${{ matrix.qt_archives }} qt_modules: ${{ matrix.qt_modules }} platform: ${{ matrix.platform }} - name: Create Build Environment if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }} run: | cmake -E make_directory ${{ github.workspace }}/build - name: Configure CMake if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }} shell: bash working-directory: ${{ github.workspace }}/build run: | QT_VERSION_MAJOR=$(echo ${{ matrix.qt_version }} | cut -d'.' -f1) # Don't fiddle with dlls in Windows, it makes things complicated... EXTRA_CMAKE_FLAGS="${EXTRA_CMAKE_FLAGS} -DBUILD_SHARED_LIBS=ON" cmake $GITHUB_WORKSPACE \ -DCMAKE_BUILD_TYPE=$BUILD_TYPE \ -DUSE_QT_VERSION=$QT_VERSION_MAJOR \ -DQCORO_WITH_QTDBUS=${{ matrix.with_qtdbus }} \ -DQCORO_ENABLE_ASAN=ON \ ${EXTRA_CMAKE_FLAGS} - name: Build if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }} shell: bash working-directory: ${{ github.workspace }}/build run: | cmake --build . --config $BUILD_TYPE --parallel $(nproc) --verbose - name: Test if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }} shell: bash working-directory: ${{ github.workspace }}/build run: | QT_LOGGING_TO_CONSOLE=1 ctest -C $BUILD_TYPE \ --output-on-failure \ --verbose \ --output-junit ${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }}.xml - name: Upload Test Results if: ${{ needs.detect_run.outputs.source_code_changed == 'true' && always() }} uses: actions/upload-artifact@v4 with: name: Unit Tests Results (${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }}) path: | ${{ github.workspace }}/build/${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }}.xml - name: Upload build logs on failure if: ${{ needs.detect_run.outputs.source_code_changed == 'true' && failure() }} uses: actions/upload-artifact@v4 with: name: build-${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }} path: build/** event_file: name: "Event File" runs-on: ubuntu-latest steps: - name: Upload uses: actions/upload-artifact@v4 with: name: Event File path: ${{ github.event_path }} qcoro-0.12.0/.github/workflows/build-windows.yml000066400000000000000000000137521477357142500216530ustar00rootroot00000000000000name: Windows CI on: push: branches: - main pull_request: types: [opened, synchronize, reopened, edited] env: BUILD_TYPE: Debug QTEST_FUNCTION_TIMEOUT: 60000 jobs: detect_run: name: Check changes outputs: { "source_code_changed": "${{ steps.source_code_changed.outputs.source_code_changed}}" } runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v4 - id: changed_files uses: tj-actions/changed-files@v46 name: Get changed files with: files_ignore: | docs/** docker/** requirements.txt mkdocs.yml README.md .github/** - id: source_code_changed name: Check for source code changes if: steps.changed_files.outputs.any_changed == 'true' run: | echo "Detected changed files:" echo "${{ steps.changed_files.outputs.all_changed_files }}" echo '::set-output name=source_code_changed::true' generate-matrix: name: Generate build matrix runs-on: ubuntu-latest outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} steps: - name: Checkout sources uses: actions/checkout@v4 - id: set-matrix name: Generate matrix run: | matrix_json=$(python3 ./.github/workflows/generate-matrix.py --platform=windows) echo "::set-output name=matrix::${matrix_json}" build: needs: [ generate-matrix, detect_run ] strategy: matrix: ${{ fromJSON(needs.generate-matrix.outputs.matrix) }} fail-fast: false runs-on: ${{ matrix.runs_on }} name: ${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }} steps: - name: Checkout sources if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }} uses: actions/checkout@v4 - name: Install Qt if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }} uses: ./.github/actions/install-qt with: qt_version: ${{ matrix.qt_version }} qt_archives: ${{ matrix.qt_archives }} qt_modules: ${{ matrix.qt_modules }} platform: ${{ matrix.platform }} - name: Create Build Environment if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }} run: | cmake -E make_directory ${{ github.workspace }}/build - name: Configure CMake if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }} shell: bash working-directory: ${{ github.workspace }}/build run: | QT_VERSION_MAJOR=$(echo ${{ matrix.qt_version }} | cut -d'.' -f1) if [[ "${{ matrix.compiler }}" == "clang-cl" ]]; then # FIXME: allow ASAN with clang-cl EXTRA_CMAKE_FLAGS="${EXTRA_CMAKE_FLAGS} -T ClangCL -DQCORO_ENABLE_ASAN=OFF" fi if [[ "${QT_VERSION_MAJOR}" == "5" ]]; then EXTRA_CMAKE_FLAGS="${EXTRA_CMAKE_FLAGS} -DQCORO_ENABLE_ASAN=OFF" fi cmake $GITHUB_WORKSPACE \ -DCMAKE_BUILD_TYPE=$BUILD_TYPE \ -DUSE_QT_VERSION=$QT_VERSION_MAJOR \ -DQCORO_WITH_QTDBUS=${{ matrix.with_qtdbus }} \ -DQCORO_ENABLE_ASAN=ON \ ${EXTRA_CMAKE_FLAGS} - name: Add ASAN DLL directory to PATH if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }} shell: pwsh run: | $installDir=(& "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath) echo "Detected MSVC install dir: \"${installDir}\"" if ("${{ matrix.compiler}}" -eq "msvc") { if (Test-Path -Path "${installDir}\VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt") { $vctoolsVersion=(Get-Content -Path "${installDir}\VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt") echo "Detected VCTools version: ${vctoolsVersion}" } else { echo "Failed to detect MSVC version" exit 1 } "${installDir}\VC\Tools\MSVC\${vctoolsVersion}\bin\HostX64\x64" >> $env:GITHUB_PATH } else { $clangVersion=((& "${installDir}\VC\Tools\Llvm\x64\bin\clang.exe" --version) | Select-String -Pattern "\d+.\d+.\d+" | % { $_.Matches } | % { $_.Value }) echo "Detected clang version: ${clangVersion}" "${installDir}\VC\Tools\Llvm\x64\lib\clang\${clangVersion}\lib\windows" >> $env:GITHUB_PATH } - name: Build if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }} shell: bash working-directory: ${{ github.workspace }}/build run: | cmake --build . --config $BUILD_TYPE --parallel $(nproc) --verbose - name: Test if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }} shell: bash working-directory: ${{ github.workspace }}/build run: | QT_LOGGING_TO_CONSOLE=1 ctest -C $BUILD_TYPE \ --output-on-failure \ --verbose \ --output-junit ${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }}.xml - name: Upload Test Results if: ${{ needs.detect_run.outputs.source_code_changed == 'true' && always() }} uses: actions/upload-artifact@v4 with: name: Unit Tests Results (${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }}) path: | ${{ github.workspace }}/build/${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }}.xml - name: Upload build logs on failure if: ${{ needs.detect_run.outputs.source_code_changed == 'true' && failure() }} uses: actions/upload-artifact@v4 with: name: build-${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }} path: build/** event_file: name: "Event File" runs-on: ubuntu-latest steps: - name: Upload uses: actions/upload-artifact@v4 with: name: Event File path: ${{ github.event_path }} qcoro-0.12.0/.github/workflows/generate-matrix.py000066400000000000000000000060031477357142500217760ustar00rootroot00000000000000import json from argparse import ArgumentParser from typing import Any, Optional qt5_config = { "archives": ["qtbase", "icu", "qtwebsockets", "qtdeclarative", "qtwebchannel", "qtlocation"], "modules": ["qtwebengine"] } qt6_config = { "archives": ["qtbase", "icu", "qtdeclarative"], "modules": ["qtwebsockets", "qtwebengine", "qtwebchannel", "qtpositioning"] } qt = [ { "version": "5.15.2", **qt5_config }, { "version": "6.2.0", **qt6_config }, { "version": "6.5.0", **qt6_config } ] platforms = [ { "name": "windows", "compilers": [ {"name": "msvc"}, {"name": "clang-cl"} ] }, { "name": "macos", "compilers": [{"name": "apple-clang"}] }, { "name": "linux", "compilers": [ { "name": "gcc", "versions": ["11", "12", "13", "14"] }, { "name": "clang", "versions": ["15", "16", "17", "20", "dev"] } ] } ] output = { "include": [] } def get_os_for_platform(platform: str) -> str: if platform == "windows": return "windows-2022" if platform == "linux": return "ubuntu-20.04" if platform == "macos": return "macos-13" raise RuntimeError(f"Invalid platform '{platform}'.") def get_base_image_for_compiler(compiler: str) -> Optional[str]: if compiler == "gcc": return "gcc" if compiler == "clang": return "debian" return None def create_configuration( qt: dict[str, Any], platform: str, compiler: str, compiler_version: str = "" ) -> dict[str, Any]: return { "qt_version": qt["version"], "qt_modules": ' '.join(qt["modules"]), "qt_archives": ' '.join(qt["archives"]), "platform": platform, "compiler": compiler, "compiler_base_image": get_base_image_for_compiler(compiler), "compiler_version": compiler_version, "compiler_full": compiler if not compiler_version else f"{compiler}-{compiler_version}", "runs_on": get_os_for_platform(platform), "with_qtdbus": "OFF" if platform == "macos" else "ON" } parser = ArgumentParser() parser.add_argument('--platform') args = parser.parse_args() filtered_platforms = list(filter(lambda p: p['name'] == args.platform, platforms)) for qt_version in qt: for platform in filtered_platforms: for compiler in platform["compilers"]: if "versions" in compiler: for compiler_version in compiler["versions"]: output["include"].append( create_configuration( qt_version, platform["name"], compiler["name"], compiler_version ) ) else: output["include"].append( create_configuration(qt_version, platform["name"], compiler["name"])) print(json.dumps(output)) qcoro-0.12.0/.github/workflows/unit_tests_results.yml000066400000000000000000000021251477357142500230360ustar00rootroot00000000000000name: Unit Test Results on: workflow_run: workflows: ["Build and Test"] types: - completed jobs: unit-test-results: name: Unit Test Results runs-on: ubuntu-latest if: github.event.workflow_run.conclusion != 'skipped' steps: - name: Download and Extract Artifacts env: GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} run: | mkdir -p artifacts && cd artifacts artifacts_url=${{ github.event.workflow_run.artifacts_url }} gh api "$artifacts_url" -q '.artifacts[] | [.name, .archive_download_url] | @tsv' | while read artifact do IFS=$'\t' read name url <<< "$artifact" gh api $url > "$name.zip" unzip -d "$name" "$name.zip" done - name: Publish Unit Test Results uses: EnricoMi/publish-unit-test-result-action@v2 with: commit: ${{ github.event.workflow_run.head_sha }} event_file: artifacts/Event File/event.json event_name: ${{ github.event.workflow_run.event }} files: "artifacts/**/*.xml" qcoro-0.12.0/.github/workflows/update-docs.yml000066400000000000000000000060571477357142500212740ustar00rootroot00000000000000name: Build and Deploy Documentation on: push: branches: - main pull_request: workflow_dispatch: jobs: detect_run: name: Check changes outputs: { "docs_changed": "${{ steps.docs_changed.outputs.docs_changed}}" } runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v4 - id: changed_files uses: tj-actions/changed-files@v46 name: Get changed files with: files: | docs/** requirements.txt mkdocs.yml README.md .github/workflows/update-docs.yml - id: docs_changed name: Check for documentatio nchanges if: steps.changed_files.outputs.any_changed == 'true' run: | echo "Detected changed files:" echo "${{ steps.changed_files.outputs.all_changed_files }}" echo "::set-output name=docs_changed::true" build: name: Build runs-on: ubuntu-latest needs: detect_run if: ${{ needs.detect_run.outputs.docs_changed == 'true' }} steps: - uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v5 with: python-version: '3.10' - name: Install dependencies run: | python -m pip install -r requirements.txt - name: Build run: | echo "{% extends \"base.html\" %}{% block analytics %} {% endblock %}" > docs/overrides/main.html mkdocs build --verbose - name: Upload artifact uses: actions/upload-artifact@v4 with: name: site path: site deploy: name: Deploy runs-on: ubuntu-latest if: ${{ github.ref == 'refs/heads/main' }} environment: name: cloudflare-pages url: ${{ steps.deploy.outputs.url }} needs: build steps: - name: Download artifact uses: actions/download-artifact@v4 with: name: site path: site - name: Deploy to Cloudflare Pages id: deploy uses: cloudflare/pages-action@1 env: CLOUDFLARE_ACCOUNT_ID: ${{ SECRETS.CLOUDFLARE_ACCOUNT_ID }} with: apiToken: ${{ SECRETS.CLOUDFLARE_PAGES_TOKEN }} accountId: ${{ SECRETS.CLOUDFLARE_ACCOUNT_ID }} projectName: ${{ vars.CLOUDFLARE_PAGES_NAME }} directory: ./site githubToken: ${{ SECRETS.GITHUB_TOKEN }} qcoro-0.12.0/.gitignore000066400000000000000000000002131477357142500147200ustar00rootroot00000000000000build/ build-*/ .*.swp .ccls-cache .cache compile_commands.json /.vs /.vscode /CMakeSettings.json # Python (from mkdocs) __pycache__ venv qcoro-0.12.0/.reuse/000077500000000000000000000000001477357142500141355ustar00rootroot00000000000000qcoro-0.12.0/.reuse/dep5000066400000000000000000000004331477357142500147150ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: QCoro Upstream-Contact: Daniel Vrátil Source: https://qcoro.github.io # Sample paragraph, commented out: # # Files: src/* # Copyright: $YEAR $NAME <$CONTACT> # License: ... qcoro-0.12.0/CMakeLists.txt000066400000000000000000000165471477357142500155110ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.18.4) set(qcoro_VERSION 0.12.0) set(qcoro_SOVERSION 0) project(qcoro LANGUAGES CXX VERSION ${qcoro_VERSION}) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) include(GNUInstallDirs) include(CTest) include(FeatureSummary) #-----------------------------------------------------------# # Options #-----------------------------------------------------------# # Allow QCORO_BUILD_TESTING to override BUILD_TESTING, but default to BUILD_TESTING if not set if (DEFINED QCORO_BUILD_TESTING) set(BUILD_TESTING ${QCORO_BUILD_TESTING}) endif() option(QCORO_BUILD_EXAMPLES "Build examples" ON) add_feature_info(Examples QCORO_BUILD_EXAMPLES "Build examples") option(QCORO_BUILD_TESTING "Build QCoro tests" ${BUILD_TESTING}) add_feature_info(Testing QCORO_BUILD_TESTING "Build QCoro tests") option(QCORO_ENABLE_ASAN "Build with AddressSanitizer" OFF) add_feature_info(Asan QCORO_ENABLE_ASAN "Build with AddressSanitizer") option(QCORO_DISABLE_DEPRECATED_TASK_H "Disable deprecated task.h header" OFF) if(WIN32 OR APPLE OR ANDROID) option(QCORO_WITH_QTDBUS "Build QtDBus support" OFF) else() option(QCORO_WITH_QTDBUS "Build QtDBus support" ON) endif() add_feature_info(QtDBus QCORO_WITH_QTDBUS "Build QtDBus support") option(QCORO_WITH_QTNETWORK "Build QtNetwork support" ON) add_feature_info(QtNetwork QCORO_WITH_QTNETWORK "Build QtNetwork support") option(QCORO_WITH_QTWEBSOCKETS "Build QtWebSockets support" ON) add_feature_info(QtWebSockets QCORO_WITH_QTWEBSOCKETS "Build QtWebSockets support") option(QCORO_WITH_QTQUICK "Build QtQuick support" ON) add_feature_info(QtQuick QCORO_WITH_QTQUICK "Build QtQuick support") option(QCORO_WITH_QML "Build QML integration features" ON) add_feature_info(QtQml QCORO_WITH_QML "Build QML integration features") option(QCORO_WITH_QTTEST "Build QtTest support" ON) add_feature_info(QtTest QCORO_WITH_QTTEST "Build QtTest support") #-----------------------------------------------------------# # Dependencies #-----------------------------------------------------------# set(THREADS_PREFER_PTHREAD_FLAG TRUE) find_package(Threads REQUIRED) include(cmake/CheckAtomic.cmake) set(REQUIRED_QT_COMPONENTS Core) set(REQUIRED_QT5_COMPONENTS) set(REQUIRED_QT6_COMPONENTS) if (QCORO_WITH_QTDBUS) list(APPEND REQUIRED_QT_COMPONENTS DBus) endif() if (QCORO_WITH_QTNETWORK) list(APPEND REQUIRED_QT_COMPONENTS Network) endif() if (QCORO_WITH_QTWEBSOCKETS) list(APPEND REQUIRED_QT_COMPONENTS WebSockets) endif() if (QCORO_WITH_QTQUICK) list(APPEND REQUIRED_QT_COMPONENTS Gui Quick QuickPrivate) endif() if (QCORO_WITH_QML) list(APPEND REQUIRED_QT_COMPONENTS Qml) # Qt6 needs access to private API list(APPEND REQUIRED_QT6_COMPONENTS QmlPrivate) endif() if (QCORO_WITH_QTTEST) list(APPEND REQUIRED_QT_COMPONENTS Test) endif() if (QCORO_BUILD_EXAMPLES) list(APPEND REQUIRED_QT_COMPONENTS Widgets Concurrent) endif() if (BUILD_TESTING) list(APPEND REQUIRED_QT_COMPONENTS Test Concurrent) endif() set(MIN_REQUIRED_QT5_VERSION "5.12") set(MIN_REQUIRED_QT6_VERSION "6.2.0") include(cmake/QCoroFindQt.cmake) # Find Qt. If USE_QT_VERSION is not set, it will try to look for Qt6 first # and fallback to Qt5 otherwise. qcoro_find_qt( QT_VERSION "${USE_QT_VERSION}" COMPONENTS "${REQUIRED_QT_COMPONENTS}" QT5_COMPONENTS "${REQUIRED_QT5_COMPONENTS}" QT6_COMPONENTS "${REQUIRED_QT6_COMPONENTS}" FOUND_VER_VAR QT_VERSION_MAJOR ) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/config.h ) #-----------------------------------------------------------# # Compiler Settings #-----------------------------------------------------------# set(CMAKE_CXX_STANDARD 20) set(CMAKE_AUTOMOC ON) if (MSVC) if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") # Explicitly enable exceptions support for clang-cl (it's only enabled by CMake when targeting the Windows-MSVC platform, # see https://github.com/danvratil/qcoro/issues/90 for details) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc") endif() else() if (ANDROID) include(DetectAndroidNDKVersion) detectAndroidNDKVersion(NDK_VERSION) if ("${NDK_VERSION}" VERSION_LESS 26) # Android NDK < 26 ships mismatching versions of clang and libc++ - libc++ is older and doesn't have coroutine support # which forces us to use coroutines from std::experimental. But the clang itself is newer and emits an error about # std::experimental coroutines being deprecated in LLVM 14. This option is needed to suppress the error. add_compile_options(-Wno-error=deprecated-experimental-coroutine) endif() endif() endif() if (QCORO_ENABLE_ASAN) if (MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /INCREMENTAL:NO") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /INCREMENTAL:NO") if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address") else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fsanitize=address") endif() else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fsanitize-recover=address") endif() endif() include(qcoro/QCoroMacros.cmake) qcoro_enable_coroutines() include(cmake/CodeCoverage.cmake) add_code_coverage() add_code_coverage_all_targets(EXCLUDE "${CMAKE_BINARY_DIR}" tests/utils/*) #-----------------------------------------------------------# # Definitions #-----------------------------------------------------------# # debug suffixes for qmake compatibility if(WIN32) set(CMAKE_DEBUG_POSTFIX "d") elseif(APPLE) set(CMAKE_DEBUG_POSTFIX "_debug") else() set(CMAKE_DEBUG_POSTFIX "") endif() set(QCORO_TARGET_PREFIX "QCoro${QT_VERSION_MAJOR}") set(QCORO_INSTALL_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}/qcoro${QT_VERSION_MAJOR}") #-----------------------------------------------------------# # Sources #-----------------------------------------------------------# set(QCORO_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) add_subdirectory(qcoro) if (QCORO_BUILD_EXAMPLES) add_subdirectory(examples) endif() if (QCORO_BUILD_TESTING) add_subdirectory(tests) endif() #-----------------------------------------------------------# # Installation #-----------------------------------------------------------# include(CMakePackageConfigHelpers) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/config.h DESTINATION ${QCORO_INSTALL_INCLUDEDIR}/qcoro COMPONENT Devel ) configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/QCoroConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/QCoro${QT_VERSION_MAJOR}Config.cmake" INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/QCoro${QT_VERSION_MAJOR}" PATH_VARS CMAKE_INSTALL_INCLUDEDIR ) write_basic_package_version_file( "${CMAKE_CURRENT_BINARY_DIR}/QCoro${QT_VERSION_MAJOR}ConfigVersion.cmake" VERSION ${qcoro_VERSION} COMPATIBILITY SameMajorVersion ) install( FILES "${CMAKE_CURRENT_BINARY_DIR}/QCoro${QT_VERSION_MAJOR}Config.cmake" "${CMAKE_CURRENT_BINARY_DIR}/QCoro${QT_VERSION_MAJOR}ConfigVersion.cmake" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/QCoro${QT_VERSION_MAJOR}" COMPONENT Devel ) #-----------------------------------------------------------# # Summary #-----------------------------------------------------------# feature_summary(FATAL_ON_MISSING_REQUIRED_PACKAGES WHAT ALL) qcoro-0.12.0/LICENSE000066400000000000000000000021011477357142500137330ustar00rootroot00000000000000MIT License Copyright (c) 2022 Daniel Vrátil Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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 AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. qcoro-0.12.0/LICENSES/000077500000000000000000000000001477357142500141415ustar00rootroot00000000000000qcoro-0.12.0/LICENSES/BSD-3-Clause.txt000066400000000000000000000026641477357142500166740ustar00rootroot00000000000000Copyright (c) . Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. qcoro-0.12.0/LICENSES/GFDL-1.3-or-later.txt000066400000000000000000000544071477357142500174520ustar00rootroot00000000000000GNU Free Documentation License Version 1.3, 3 November 2008 Copyright (C) 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. 0. PREAMBLE The purpose of this License is to make a manual, textbook, or other functional and useful document "free" in the sense of freedom: to assure everyone the effective freedom to copy and redistribute it, with or without modifying it, either commercially or noncommercially. Secondarily, this License preserves for the author and publisher a way to get credit for their work, while not being considered responsible for modifications made by others. This License is a kind of "copyleft", which means that derivative works of the document must themselves be free in the same sense. It complements the GNU General Public License, which is a copyleft license designed for free software. We have designed this License in order to use it for manuals for free software, because free software needs free documentation: a free program should come with manuals providing the same freedoms that the software does. But this License is not limited to software manuals; it can be used for any textual work, regardless of subject matter or whether it is published as a printed book. We recommend this License principally for works whose purpose is instruction or reference. 1. APPLICABILITY AND DEFINITIONS This License applies to any manual or other work, in any medium, that contains a notice placed by the copyright holder saying it can be distributed under the terms of this License. Such a notice grants a world-wide, royalty-free license, unlimited in duration, to use that work under the conditions stated herein. The "Document", below, refers to any such manual or work. Any member of the public is a licensee, and is addressed as "you". You accept the license if you copy, modify or distribute the work in a way requiring permission under copyright law. A "Modified Version" of the Document means any work containing the Document or a portion of it, either copied verbatim, or with modifications and/or translated into another language. A "Secondary Section" is a named appendix or a front-matter section of the Document that deals exclusively with the relationship of the publishers or authors of the Document to the Document's overall subject (or to related matters) and contains nothing that could fall directly within that overall subject. (Thus, if the Document is in part a textbook of mathematics, a Secondary Section may not explain any mathematics.) The relationship could be a matter of historical connection with the subject or with related matters, or of legal, commercial, philosophical, ethical or political position regarding them. The "Invariant Sections" are certain Secondary Sections whose titles are designated, as being those of Invariant Sections, in the notice that says that the Document is released under this License. If a section does not fit the above definition of Secondary then it is not allowed to be designated as Invariant. The Document may contain zero Invariant Sections. If the Document does not identify any Invariant Sections then there are none. The "Cover Texts" are certain short passages of text that are listed, as Front-Cover Texts or Back-Cover Texts, in the notice that says that the Document is released under this License. A Front-Cover Text may be at most 5 words, and a Back-Cover Text may be at most 25 words. A "Transparent" copy of the Document means a machine-readable copy, represented in a format whose specification is available to the general public, that is suitable for revising the document straightforwardly with generic text editors or (for images composed of pixels) generic paint programs or (for drawings) some widely available drawing editor, and that is suitable for input to text formatters or for automatic translation to a variety of formats suitable for input to text formatters. A copy made in an otherwise Transparent file format whose markup, or absence of markup, has been arranged to thwart or discourage subsequent modification by readers is not Transparent. An image format is not Transparent if used for any substantial amount of text. A copy that is not "Transparent" is called "Opaque". Examples of suitable formats for Transparent copies include plain ASCII without markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly available DTD, and standard-conforming simple HTML, PostScript or PDF designed for human modification. Examples of transparent image formats include PNG, XCF and JPG. Opaque formats include proprietary formats that can be read and edited only by proprietary word processors, SGML or XML for which the DTD and/or processing tools are not generally available, and the machine-generated HTML, PostScript or PDF produced by some word processors for output purposes only. The "Title Page" means, for a printed book, the title page itself, plus such following pages as are needed to hold, legibly, the material this License requires to appear in the title page. For works in formats which do not have any title page as such, "Title Page" means the text near the most prominent appearance of the work's title, preceding the beginning of the body of the text. The "publisher" means any person or entity that distributes copies of the Document to the public. A section "Entitled XYZ" means a named subunit of the Document whose title either is precisely XYZ or contains XYZ in parentheses following text that translates XYZ in another language. (Here XYZ stands for a specific section name mentioned below, such as "Acknowledgements", "Dedications", "Endorsements", or "History".) To "Preserve the Title" of such a section when you modify the Document means that it remains a section "Entitled XYZ" according to this definition. The Document may include Warranty Disclaimers next to the notice which states that this License applies to the Document. These Warranty Disclaimers are considered to be included by reference in this License, but only as regards disclaiming warranties: any other implication that these Warranty Disclaimers may have is void and has no effect on the meaning of this License. 2. VERBATIM COPYING You may copy and distribute the Document in any medium, either commercially or noncommercially, provided that this License, the copyright notices, and the license notice saying this License applies to the Document are reproduced in all copies, and that you add no other conditions whatsoever to those of this License. You may not use technical measures to obstruct or control the reading or further copying of the copies you make or distribute. However, you may accept compensation in exchange for copies. If you distribute a large enough number of copies you must also follow the conditions in section 3. You may also lend copies, under the same conditions stated above, and you may publicly display copies. 3. COPYING IN QUANTITY If you publish printed copies (or copies in media that commonly have printed covers) of the Document, numbering more than 100, and the Document's license notice requires Cover Texts, you must enclose the copies in covers that carry, clearly and legibly, all these Cover Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on the back cover. Both covers must also clearly and legibly identify you as the publisher of these copies. The front cover must present the full title with all words of the title equally prominent and visible. You may add other material on the covers in addition. Copying with changes limited to the covers, as long as they preserve the title of the Document and satisfy these conditions, can be treated as verbatim copying in other respects. If the required texts for either cover are too voluminous to fit legibly, you should put the first ones listed (as many as fit reasonably) on the actual cover, and continue the rest onto adjacent pages. If you publish or distribute Opaque copies of the Document numbering more than 100, you must either include a machine-readable Transparent copy along with each Opaque copy, or state in or with each Opaque copy a computer-network location from which the general network-using public has access to download using public-standard network protocols a complete Transparent copy of the Document, free of added material. If you use the latter option, you must take reasonably prudent steps, when you begin distribution of Opaque copies in quantity, to ensure that this Transparent copy will remain thus accessible at the stated location until at least one year after the last time you distribute an Opaque copy (directly or through your agents or retailers) of that edition to the public. It is requested, but not required, that you contact the authors of the Document well before redistributing any large number of copies, to give them a chance to provide you with an updated version of the Document. 4. MODIFICATIONS You may copy and distribute a Modified Version of the Document under the conditions of sections 2 and 3 above, provided that you release the Modified Version under precisely this License, with the Modified Version filling the role of the Document, thus licensing distribution and modification of the Modified Version to whoever possesses a copy of it. In addition, you must do these things in the Modified Version: A. Use in the Title Page (and on the covers, if any) a title distinct from that of the Document, and from those of previous versions (which should, if there were any, be listed in the History section of the Document). You may use the same title as a previous version if the original publisher of that version gives permission. B. List on the Title Page, as authors, one or more persons or entities responsible for authorship of the modifications in the Modified Version, together with at least five of the principal authors of the Document (all of its principal authors, if it has fewer than five), unless they release you from this requirement. C. State on the Title page the name of the publisher of the Modified Version, as the publisher. D. Preserve all the copyright notices of the Document. E. Add an appropriate copyright notice for your modifications adjacent to the other copyright notices. F. Include, immediately after the copyright notices, a license notice giving the public permission to use the Modified Version under the terms of this License, in the form shown in the Addendum below. G. Preserve in that license notice the full lists of Invariant Sections and required Cover Texts given in the Document's license notice. H. Include an unaltered copy of this License. I. Preserve the section Entitled "History", Preserve its Title, and add to it an item stating at least the title, year, new authors, and publisher of the Modified Version as given on the Title Page. If there is no section Entitled "History" in the Document, create one stating the title, year, authors, and publisher of the Document as given on its Title Page, then add an item describing the Modified Version as stated in the previous sentence. J. Preserve the network location, if any, given in the Document for public access to a Transparent copy of the Document, and likewise the network locations given in the Document for previous versions it was based on. These may be placed in the "History" section. You may omit a network location for a work that was published at least four years before the Document itself, or if the original publisher of the version it refers to gives permission. K. For any section Entitled "Acknowledgements" or "Dedications", Preserve the Title of the section, and preserve in the section all the substance and tone of each of the contributor acknowledgements and/or dedications given therein. L. Preserve all the Invariant Sections of the Document, unaltered in their text and in their titles. Section numbers or the equivalent are not considered part of the section titles. M. Delete any section Entitled "Endorsements". Such a section may not be included in the Modified Version. N. Do not retitle any existing section to be Entitled "Endorsements" or to conflict in title with any Invariant Section. O. Preserve any Warranty Disclaimers. If the Modified Version includes new front-matter sections or appendices that qualify as Secondary Sections and contain no material copied from the Document, you may at your option designate some or all of these sections as invariant. To do this, add their titles to the list of Invariant Sections in the Modified Version's license notice. These titles must be distinct from any other section titles. You may add a section Entitled "Endorsements", provided it contains nothing but endorsements of your Modified Version by various parties--for example, statements of peer review or that the text has been approved by an organization as the authoritative definition of a standard. You may add a passage of up to five words as a Front-Cover Text, and a passage of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover Text may be added by (or through arrangements made by) any one entity. If the Document already includes a cover text for the same cover, previously added by you or by arrangement made by the same entity you are acting on behalf of, you may not add another; but you may replace the old one, on explicit permission from the previous publisher that added the old one. The author(s) and publisher(s) of the Document do not by this License give permission to use their names for publicity for or to assert or imply endorsement of any Modified Version. 5. COMBINING DOCUMENTS You may combine the Document with other documents released under this License, under the terms defined in section 4 above for modified versions, provided that you include in the combination all of the Invariant Sections of all of the original documents, unmodified, and list them all as Invariant Sections of your combined work in its license notice, and that you preserve all their Warranty Disclaimers. The combined work need only contain one copy of this License, and multiple identical Invariant Sections may be replaced with a single copy. If there are multiple Invariant Sections with the same name but different contents, make the title of each such section unique by adding at the end of it, in parentheses, the name of the original author or publisher of that section if known, or else a unique number. Make the same adjustment to the section titles in the list of Invariant Sections in the license notice of the combined work. In the combination, you must combine any sections Entitled "History" in the various original documents, forming one section Entitled "History"; likewise combine any sections Entitled "Acknowledgements", and any sections Entitled "Dedications". You must delete all sections Entitled "Endorsements". 6. COLLECTIONS OF DOCUMENTS You may make a collection consisting of the Document and other documents released under this License, and replace the individual copies of this License in the various documents with a single copy that is included in the collection, provided that you follow the rules of this License for verbatim copying of each of the documents in all other respects. You may extract a single document from such a collection, and distribute it individually under this License, provided you insert a copy of this License into the extracted document, and follow this License in all other respects regarding verbatim copying of that document. 7. AGGREGATION WITH INDEPENDENT WORKS A compilation of the Document or its derivatives with other separate and independent documents or works, in or on a volume of a storage or distribution medium, is called an "aggregate" if the copyright resulting from the compilation is not used to limit the legal rights of the compilation's users beyond what the individual works permit. When the Document is included in an aggregate, this License does not apply to the other works in the aggregate which are not themselves derivative works of the Document. If the Cover Text requirement of section 3 is applicable to these copies of the Document, then if the Document is less than one half of the entire aggregate, the Document's Cover Texts may be placed on covers that bracket the Document within the aggregate, or the electronic equivalent of covers if the Document is in electronic form. Otherwise they must appear on printed covers that bracket the whole aggregate. 8. TRANSLATION Translation is considered a kind of modification, so you may distribute translations of the Document under the terms of section 4. Replacing Invariant Sections with translations requires special permission from their copyright holders, but you may include translations of some or all Invariant Sections in addition to the original versions of these Invariant Sections. You may include a translation of this License, and all the license notices in the Document, and any Warranty Disclaimers, provided that you also include the original English version of this License and the original versions of those notices and disclaimers. In case of a disagreement between the translation and the original version of this License or a notice or disclaimer, the original version will prevail. If a section in the Document is Entitled "Acknowledgements", "Dedications", or "History", the requirement (section 4) to Preserve its Title (section 1) will typically require changing the actual title. 9. TERMINATION You may not copy, modify, sublicense, or distribute the Document except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, or distribute it is void, and will automatically terminate your rights under this License. However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, receipt of a copy of some or all of the same material does not give you any rights to use it. 10. FUTURE REVISIONS OF THIS LICENSE The Free Software Foundation may publish new, revised versions of the GNU Free Documentation License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. See http://www.gnu.org/copyleft/. Each version of the License is given a distinguishing version number. If the Document specifies that a particular numbered version of this License "or any later version" applies to it, you have the option of following the terms and conditions either of that specified version or of any later version that has been published (not as a draft) by the Free Software Foundation. If the Document does not specify a version number of this License, you may choose any version ever published (not as a draft) by the Free Software Foundation. If the Document specifies that a proxy can decide which future versions of this License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Document. 11. RELICENSING "Massive Multiauthor Collaboration Site" (or "MMC Site") means any World Wide Web server that publishes copyrightable works and also provides prominent facilities for anybody to edit those works. A public wiki that anybody can edit is an example of such a server. A "Massive Multiauthor Collaboration" (or "MMC") contained in the site means any set of copyrightable works thus published on the MMC site. "CC-BY-SA" means the Creative Commons Attribution-Share Alike 3.0 license published by Creative Commons Corporation, a not-for-profit corporation with a principal place of business in San Francisco, California, as well as future copyleft versions of that license published by that same organization. "Incorporate" means to publish or republish a Document, in whole or in part, as part of another Document. An MMC is "eligible for relicensing" if it is licensed under this License, and if all works that were first published under this License somewhere other than this MMC, and subsequently incorporated in whole or in part into the MMC, (1) had no cover texts or invariant sections, and (2) were thus incorporated prior to November 1, 2008. The operator of an MMC Site may republish an MMC contained in the site under CC-BY-SA on the same site at any time before August 1, 2009, provided the MMC is eligible for relicensing. ADDENDUM: How to use this License for your documents To use this License in a document you have written, include a copy of the License in the document and put the following copyright and license notices just after the title page: Copyright (c) YEAR YOUR NAME. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License". If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, replace the "with...Texts." line with this: with the Invariant Sections being LIST THEIR TITLES, with the Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. If you have Invariant Sections without Cover Texts, or some other combination of the three, merge those two alternatives to suit the situation. If your document contains nontrivial examples of program code, we recommend releasing these examples in parallel under your choice of free software license, such as the GNU General Public License, to permit their use in free software. qcoro-0.12.0/LICENSES/MIT.txt000066400000000000000000000021011477357142500153250ustar00rootroot00000000000000MIT License Copyright (c) 2021 Daniel Vrátil Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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 AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. qcoro-0.12.0/QCoroConfig.cmake.in000066400000000000000000000031611477357142500165150ustar00rootroot00000000000000if (CMAKE_VERSION VERSION_LESS 3.1.0) message(FATAL_ERROR \"QCoro@QT_VERSION_MAJOR@ requires at least CMake version 3.1.0\") endif() if (NOT QCoro@QT_VERSION_MAJOR@_FIND_COMPONENTS) set(QCoro@QT_VERSION_MAJOR@_NOT_FOUND_MESSAGE "The QCoro@QT_VERSION_MAJOR@ package requires at least one component") set(QCoro@QT_VERSION_MAJOR@_FOUND FALSE) return() endif() set(_QCoro_FIND_PARTS_REQUIRED) if (QCoro@QT_VERSION_MAJOR@_FIND_REQUIRED) set(_QCoro_FIND_PARTS_REQUIRED REQUIRED) endif() set(_QCoro_FIND_PARTS_QUIET) if (QCoro@QT_VERSION_MAJOR@_FIND_QUIET) set(_QCoro_FIND_PARTS_QUIET QUIET) endif() get_filename_component(_qcoro_install_prefix "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE) set(_QCoro_NOTFOUND_MESSAGE) foreach(module ${QCoro@QT_VERSION_MAJOR@_FIND_COMPONENTS}) find_package(QCoro@QT_VERSION_MAJOR@${module} ${_QCoro_FIND_PARTS_QUIET} ${_QCoro_FIND_PARTS_REQUIRED} PATHS ${_qcoro_install_prefix} NO_DEFAULT_PATH ) if (NOT QCoro@QT_VERSION_MAJOR@${module}_FOUND) if (QCoro@QT_VERSION_MAJOR@_FIND_REQUIRED_${module}) set(_QCoro_NOTFOUND_MESSAGE "${_QCoro_NOTFOUND_MESSAGE}Failed to find QCoro component \"${module}\" config file at \"${_qcoro_install_prefix}\"\n") elseif (NOT QCoro@QT_VERSION_MAJOR@_FIND_QUIETLY) message(WARNING "Failed to find QCoro@QT_VERSION_MAJOR@ component \"${module}\" config file at \"${_qcoro_install_prefix}\"") endif() endif() endforeach() if (_QCoro_NOTFOUND_MESSAGE) set(QCoro@QT_VERSION_MAJOR@_NOT_FOUND_MESSAGE "${_QCoro_NOTFOUND_MESSAGE}") set(QCoro@QT_VERSION_MAJOR@_FOUND FALSE) endif() qcoro-0.12.0/README.md000066400000000000000000000203641477357142500142200ustar00rootroot00000000000000[![Linux CI](https://github.com/qcoro/qcoro/actions/workflows/build-linux.yml/badge.svg)](https://github.com/qcoro/qcoro/actions/workflows/build-linux.yml) [![Windows CI](https://github.com/qcoro/qcoro/actions/workflows/build-windows.yml/badge.svg)](https://github.com/qcoro/qcoro/actions/workflows/build-windows.yml) [![MacOS CI](https://github.com/qcoro/qcoro/actions/workflows/build-macos.yml/badge.svg)](https://github.com/qcoro/qcoro/actions/workflows/build-macos.yml) [![Docs build](https://github.com/qcoro/qcoro/actions/workflows/update-docs.yml/badge.svg?branch=main)](https://github.com/qcoro/qcoro/actions/workflows/update-docs.yml) [![Latest release](https://img.shields.io/github/v/release/qcoro/qcoro?label=%F0%9F%93%A6%20Release)](https://github.com/qcoro/qcoro/releases) ![License: MIT](https://img.shields.io/badge/%E2%9A%96%EF%B8%8F%20License-MIT-brightgreen) ![C++20](https://img.shields.io/badge/C%2B%2B-20-%2300599C?logo=cplusplus) ![Supported Compilers](https://img.shields.io/badge/%E2%9A%99%EF%B8%8F%20Compilers-GCC%2C%20clang%2C%20MSVC-informational) # QCoro - Coroutines for Qt5 and Qt6 The QCoro library provides set of tools to make use of C++20 coroutines with Qt. Take a look at the example below to see what an amazing thing coroutines are: ```cpp QNetworkAccessManager networkAccessManager; // co_await the reply - the coroutine is suspended until the QNetworkReply is finished. // While the coroutine is suspended, *the Qt event loop runs as usual*. const QNetworkReply *reply = co_await networkAccessManager.get(url); // Once the reply is finished, your code resumes here as if nothing amazing has just happened ;-) const auto data = reply->readAll(); ``` It requires a compiler with support for the couroutines TS, see [documentation](https://qcoro.dev/#supported-compilers) for a list of supported compilers and versions. ## Documentation 👉 📘 [Documentation](https://qcoro.dev/) ## Supported Qt Types QCoro provides the tools necessary to make easy use of C++20 coroutines with Qt. The cornerstone of the library is `QCoro::Task`, which represents an executed coroutine and allows the result of the coroutine to be asynchronously awaited by its caller. Additionally, QCoro provides a set of wrappers for common Qt types, such as `QTimer`, `QNetworkReply`, `QDBusPendingCall`, `QFuture` and others, that allow to `co_await` their asynchronous operations directly. Additionally, there's a magical `qCoro()` function that can wrap many native Qt functions and types to make them coroutine-friendly. Go check the [documentation](https://qcoro.dev/reference) for a full list of all supported features and Qt types. ### `QDBusPendingCall` QCoro can wait for an asynchronous D-Bus call to finish. There's no need to use `QDBusPendingCallWatcher` with QCoro - just `co_await` the result instead. While co_awaiting, the Qt event loop runs as usual. ```cpp QDBusInterface remoteServiceInterface{serviceName, objectPath, interface}; const QDBusReply isReady = co_await remoteServiceInterface.asyncCall(QStringLiteral("isReady")); ``` 📘 [Full documentation here](https://qcoro.dev/reference/dbus/qdbuspendingcall). ### `QFuture` QFuture represents a result of an asynchronous task. Normally you have to use `QFutureWatcher` to get notified when the future is ready. With QCoro, you can just `co_await` it! ```cpp const QFuture task1 = QtConcurrent::run(....); const QFuture task2 = QtConcurrent::run(....); const int a = co_await task1; const int b = co_await task2; co_return a + b; ``` 📘 [Full documentation here](https://qcoro.dev/reference/core/qfuture). ### `QNetworkReply` Doing network requests with Qt can be tedious - the signal/slot approach breaks the flow of your code. Chaining requests and error handling quickly become mess and your code is broken into numerous functions. But not with QCoro, where you can simply `co_await` the `QNetworkReply` to finish: ```cpp QNetworkAccessManager qnam; QNetworkReply *reply = qnam.get(QStringLiteral("https://github.com/qcoro/qcoro")); const auto contents = co_await reply; reply->deleteLater(); if (reply->error() != QNetworkReply::NoError) { co_return handleError(reply); } const auto link = findLinkInReturnedHtmlCode(contents); reply = qnam.get(link); const auto data = co_await reply; reply->deleteLater(); if (reply->error() != QNetworkReply::NoError) { co_return handleError(reply); } ... ``` 📘 [Full documentation here](https://qcoro.dev/reference/network/qnetworkreply). ### `QTimer` Maybe you want to delay executing your code for a second, maybe you want to execute some code in repeated interval. This becomes super-trivial with `co_await`: ```cpp QTimer timer; timer.setInterval(1s); timer.start(); for (int i = 1; i <= 100; ++i) { co_await timer; qDebug() << "Waiting for " << i << " seconds..."; } qDebug() << "Done!"; ``` 📘 [Full documentation here](https://qcoro.dev/reference/core/qtimer). ### `QIODevice` `QIODevice` is a base-class for many classes in Qt that allow data to be asynchronously written and read. How do you find out that there are data ready to be read? You could connect to `QIODevice::readyRead()` singal, or you could use QCoro and `co_await` the object: ```cpp socket->write("PING"); // Waiting for "pong" const auto data = co_await socket; co_return calculateLatency(data); ``` 📘 [Full documentation here](https://qcoro.dev/reference/core/qiodevice). ### ...and more! Go check the [full documentation](https://qcoro.dev) to learn more. ## .then() continuations Sometimes it's not possible to use `co_await` to handle result of a coroutine - usually when interfacing with a 3rd party code that does not support coroutines. In those scenarios it's possible to chain a continuation callback to the coroutine which will get invoked asynchronously when the coroutine finishes. ```cpp void regularFunction() { someCoroutineReturningInt().then([](int result) { // handle result }); } ``` The continuation callback can also be a coroutine and the result of the entire expression is Task where T is the return type of the continuation. Thanks to that it's possible to `co_await` the entire chain, or chain multiple `.then()` continuations. 📘 [Full documentation here](https://qcoro.dev/reference/coro/task). ## Generators Generator is a coroutine that lazily produces multiple values. While there's nothing Qt-specific, QCoro provides the necessary tools for users to create custom generators in their Qt applications. QCoro provides API for both synchronous generators (`QCoro::Generator`) and asynchronous generators (`QCoro::AsyncGenerator`). Both generators provide container-like API: `begin()` and `end()` member functions that return iterator-like objects, which is well-known and established API and makes generators compatible with existing algorithms. ```cpp QCoro::Generator fibonacci() { quint64 a = 0, b = 0; Q_FOREVER { co_yield b; const auto tmp = b; a = b; b += tmp; } } void printFib(quint64 max) { for (auto fib : fibonacci()) { if (fib > max) { break; } std::cout << fib << std::endl; } } ``` 📘 [Full documentation here](https://qcoro.dev/reference/coro/generator). ## License ```text MIT License Copyright (c) 2022 Daniel Vrátil Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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 AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` qcoro-0.12.0/cmake/000077500000000000000000000000001477357142500140145ustar00rootroot00000000000000qcoro-0.12.0/cmake/AddQCoroLibrary.cmake000066400000000000000000000216321477357142500200030ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2022 Daniel Vrátil # # SPDX-License-Identifier: MIT include(GenerateHeaders) include(GenerateExportHeader) include(GenerateModuleConfigFile) include(ECMGeneratePriFile) function(set_target_defaults target_name) set(DEFAULT_QT_DEFINITIONS QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII QT_NO_URL_CAST_FROM_STRING QT_NO_CAST_FROM_BYTEARRAY QT_USE_STRINGBUILDER QT_NO_NARROWING_CONVERSIONS_IN_CONNECT QT_NO_KEYWORDS QT_NO_FOREACH) get_target_property(target_type ${target_name} TYPE) if (target_type STREQUAL "INTERFACE_LIBRARY") # We can't set compile definitions for interface libraries as that would leak into user code return() endif() target_compile_definitions(${target_name} PRIVATE ${DEFAULT_QT_DEFINITIONS}) if (NOT WIN32) # strict iterators on MSVC only work when Qt itself is also built with them, # which is not usually the case. Otherwise there are linking issues. target_compile_definitions(${target_name} PRIVATE QT_STRICT_ITERATORS) endif() string(TOLOWER "${CMAKE_BUILD_TYPE}" build_type_lowercase) if ("${build_type_lowercase}" STREQUAL "debug") if (MSVC) target_compile_options(${target_name} PRIVATE /W4 /WX) # Disable warning C5054: "operator '&': deprecated between enumerations of different types" caused by QtWidgets/qsizepolicy.h # Disable warning C4127: "conditional expression is constant" caused by QtCore/qiterable.h target_compile_options(${target_name} PRIVATE /wd5054 /wd4127) if ("${QT_VERSION_MAJOR}" STREQUAL "6" AND "${Qt6_VERSION}" VERSION_GREATER_EQUAL "6.4.0" AND "${Qt6_VERSION}" VERSION_LESS "6.5.3") # Disable warning C4702: "unreachable code" caused by QtTest/qtestcase.h - fixed in Qt 6.5.3 target_compile_options(${target_name} PRIVATE /wd4702) endif() else() target_compile_options(${target_name} PRIVATE -Wall -Wextra -Werror -pedantic -Wno-language-extension-token) endif() endif() endfunction() function(add_qcoro_library) function(prefix_libraries) set(oneValueArgs PREFIX OUTPUT) set(multiValueArgs LIBRARIES) cmake_parse_arguments(prf "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) set(_libs) foreach(libname ${prf_LIBRARIES}) if ("${libname}" MATCHES "PUBLIC|PRIVATE|INTERFACE") list(APPEND _libs "${libname}") else() list(APPEND _libs "${prf_PREFIX}::${libname}") endif() endforeach() set(${prf_OUTPUT} ${_libs} PARENT_SCOPE) endfunction() function(process_qmake_deps) set(oneValueArgs PREFIX OUTPUT) set(multiValueArgs LIBRARIES) cmake_parse_arguments(pqd "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) set(_libs_priv FALSE) set(_deps) foreach (dep ${pqd_LIBRARIES}) if ("${dep}" MATCHES "PUBLIC|INTERFACE|public|interface") set(_libs_priv FALSE) continue() elseif ("${dep}" MATCHES "PRIVATE|private") set(_libs_priv TRUE) continue() endif() if (NOT _libs_priv) set(_deps "${_deps} ${pqd_PREFIX}${dep}") endif() endforeach() set(${pqd_OUTPUT} ${_deps} PARENT_SCOPE) endfunction() set(params INTERFACE NO_CMAKE_CONFIG) set(oneValueArgs NAME) set(multiValueArgs SOURCES CAMELCASE_HEADERS HEADERS QCORO_LINK_LIBRARIES QT_LINK_LIBRARIES) cmake_parse_arguments(LIB "${params}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) set(target_name "${QCORO_TARGET_PREFIX}${LIB_NAME}") string(TOLOWER "${target_name}" target_name_lowercase) set(target_interface) set(target_include_interface "PUBLIC") if (LIB_INTERFACE) set(target_interface "INTERFACE") set(target_include_interface "INTERFACE") endif() prefix_libraries( PREFIX ${QCORO_TARGET_PREFIX} LIBRARIES ${LIB_QCORO_LINK_LIBRARIES} OUTPUT qcoro_LIBS ) prefix_libraries( PREFIX Qt${QT_VERSION_MAJOR} LIBRARIES ${LIB_QT_LINK_LIBRARIES} OUTPUT qt_LIBS ) # TODO: How is it done in Qt? # We want to export target QCoro5::Network but so far we are exporting # QCoro5::QCoro5Network :shrug: add_library(${target_name} ${target_interface}) add_library(${QCORO_TARGET_PREFIX}::${LIB_NAME} ALIAS ${target_name}) if (LIB_SOURCES) target_sources(${target_name} PRIVATE ${LIB_SOURCES}) endif() target_include_directories( ${target_name} ${target_include_interface} $ ${target_include_interface} $ ${target_include_interface} $ ${target_include_interface} $ ${target_include_interface} $ ${target_include_interface} $ ${target_include_interface} $ ${target_include_interface} $ ) target_link_libraries(${target_name} ${qcoro_LIBS}) target_link_libraries(${target_name} ${qt_LIBS}) if(NOT HAVE_CXX_ATOMICS_WITHOUT_LIB AND NOT LIB_INTERFACE) target_link_libraries(${target_name} PUBLIC atomic) endif() set_target_properties( ${target_name} PROPERTIES EXPORT_NAME ${LIB_NAME} ) set_target_defaults(${target_name}) if (NOT LIB_INTERFACE) set_target_properties( ${target_name} PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS 1 VERSION ${qcoro_VERSION} SOVERSION ${qcoro_SOVERSION} ) target_code_coverage(${target_name} AUTO) else() target_code_coverage(${target_name} AUTO INTERFACE) endif() generate_headers( camelcase_HEADERS HEADER_NAMES ${LIB_CAMELCASE_HEADERS} OUTPUT_DIR QCoro ORIGINAL_HEADERS_VAR source_HEADERS ) if (NOT LIB_INTERFACE) string(TOUPPER "qcoro${LIB_NAME}" export_name) string(TOLOWER "${export_name}" export_file) generate_export_header( ${target_name} BASE_NAME ${export_name} ) endif() if (NOT LIB_NO_CMAKE_CONFIG) generate_cmake_module_config_file( NAME ${LIB_NAME} TARGET_NAME ${target_name} QT_DEPENDENCIES ${LIB_QT_LINK_LIBRARIES} QCORO_DEPENDENCIES ${LIB_QCORO_LINK_LIBRARIES} ) endif() string(TOLOWER "${LIB_QT_LINK_LIBRARIES}" lc_qt_link_libraries) process_qmake_deps( OUTPUT qmake_qt_deps LIBRARIES ${lc_qt_link_libraries} ) process_qmake_deps( PREFIX QCoro OUTPUT qmake_qcoro_deps LIBRARIES ${LIB_QCORO_LINK_LIBRARIES} ) set(egp_INTERFACE) if (LIB_INTERFACE) set(egp_INTERFACE "INTERFACE") endif() ecm_generate_pri_file( ${egp_INTERFACE} BASE_NAME QCoro${LIB_NAME} LIB_NAME ${target_name} VERSION ${qcoro_VERSION} INCLUDE_INSTALL_DIRS ${QCORO_INSTALL_INCLUDEDIR}/qcoro;${QCORO_INSTALL_INCLUDEDIR}/QCoro DEPS "${qmake_qt_deps} ${qmake_qcoro_deps}" ) install( TARGETS ${target_name} EXPORT ${target_name}Targets ) install( FILES ${source_HEADERS} DESTINATION ${QCORO_INSTALL_INCLUDEDIR}/qcoro/ COMPONENT Devel ) foreach(lib_header ${LIB_HEADERS}) get_filename_component(header_prefix_dir ${lib_header} DIRECTORY) install( FILES ${lib_header} DESTINATION ${QCORO_INSTALL_INCLUDEDIR}/qcoro/${header_prefix_dir} COMPONENT Devel ) endforeach() install( FILES ${camelcase_HEADERS} DESTINATION ${QCORO_INSTALL_INCLUDEDIR}/QCoro/ COMPONENT Devel ) if (NOT LIB_INTERFACE) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${export_file}_export.h DESTINATION ${QCORO_INSTALL_INCLUDEDIR}/qcoro COMPONENT Devel ) endif() install( FILES "${CMAKE_CURRENT_BINARY_DIR}/${target_name}Config.cmake" "${CMAKE_CURRENT_BINARY_DIR}/${target_name}ConfigVersion.cmake" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${target_name}" COMPONENT Devel ) install( EXPORT ${target_name}Targets DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${target_name}" FILE "${target_name}Targets.cmake" NAMESPACE ${QCORO_TARGET_PREFIX}:: COMPONENT Devel ) install( FILES "${CMAKE_CURRENT_BINARY_DIR}/qt_QCoro${LIB_NAME}.pri" DESTINATION "${ECM_MKSPECS_INSTALL_DIR}" COMPONENT Devel ) endfunction() qcoro-0.12.0/cmake/CheckAtomic.cmake000066400000000000000000000023171477357142500171730ustar00rootroot00000000000000# std::atomic may need libatomic to function correctly. INCLUDE(CheckCXXSourceCompiles) INCLUDE(CheckLibraryExists) # Sometimes linking against libatomic is required for atomic ops, if # the platform doesn't support lock-free atomics. function(check_working_cxx_atomics varname) set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -std=c++20") CHECK_CXX_SOURCE_COMPILES(" #include std::atomic x; std::atomic y; std::atomic z; int main() { ++z; ++y; return ++x; } " ${varname}) set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS}) endfunction(check_working_cxx_atomics) # Check for (non-64-bit) atomic operations. if(MSVC) set(HAVE_CXX_ATOMICS_WITHOUT_LIB True) else() # First check if atomics work without the library. check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITHOUT_LIB) # If not, check if the library exists, and atomics work with it. if(NOT HAVE_CXX_ATOMICS_WITHOUT_LIB) list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic") check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITH_LIB) if (NOT HAVE_CXX_ATOMICS_WITH_LIB) message(FATAL_ERROR "Host compiler must support std::atomic!") endif() endif() endif() qcoro-0.12.0/cmake/CodeCoverage.cmake000066400000000000000000000673771477357142500173700ustar00rootroot00000000000000# # Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. # USAGE: To enable any code coverage instrumentation/targets, the single CMake # option of `CODE_COVERAGE` needs to be set to 'ON', either by GUI, ccmake, or # on the command line. # # From this point, there are two primary methods for adding instrumentation to # targets: 1 - A blanket instrumentation by calling `add_code_coverage()`, where # all targets in that directory and all subdirectories are automatically # instrumented. 2 - Per-target instrumentation by calling # `target_code_coverage()`, where the target is given and thus only # that target is instrumented. This applies to both libraries and executables. # # To add coverage targets, such as calling `make ccov` to generate the actual # coverage information for perusal or consumption, call # `target_code_coverage()` on an *executable* target. # # Example 1: All targets instrumented # # In this case, the coverage information reported will will be that of the # `theLib` library target and `theExe` executable. # # 1a: Via global command # # ~~~ # add_code_coverage() # Adds instrumentation to all targets # # add_library(theLib lib.cpp) # # add_executable(theExe main.cpp) # target_link_libraries(theExe PRIVATE theLib) # target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target (instrumentation already added via global anyways) for generating code coverage reports. # ~~~ # # 1b: Via target commands # # ~~~ # add_library(theLib lib.cpp) # target_code_coverage(theLib) # As a library target, adds coverage instrumentation but no targets. # # add_executable(theExe main.cpp) # target_link_libraries(theExe PRIVATE theLib) # target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target and instrumentation for generating code coverage reports. # ~~~ # # Example 2: Target instrumented, but with regex pattern of files to be excluded # from report # # ~~~ # add_executable(theExe main.cpp non_covered.cpp) # target_code_coverage(theExe EXCLUDE non_covered.cpp test/*) # As an executable target, the reports will exclude the non-covered.cpp file, and any files in a test/ folder. # ~~~ # # Example 3: Target added to the 'ccov' and 'ccov-all' targets # # ~~~ # add_code_coverage_all_targets(EXCLUDE test/*) # Adds the 'ccov-all' target set and sets it to exclude all files in test/ folders. # # add_executable(theExe main.cpp non_covered.cpp) # target_code_coverage(theExe AUTO ALL EXCLUDE non_covered.cpp test/*) # As an executable target, adds to the 'ccov' and ccov-all' targets, and the reports will exclude the non-covered.cpp file, and any files in a test/ folder. # ~~~ # Options option( CODE_COVERAGE "Builds targets with code coverage instrumentation. (Requires GCC or Clang)" OFF) # Programs find_program(LLVM_COV_PATH llvm-cov) find_program(LLVM_PROFDATA_PATH llvm-profdata) find_program(LCOV_PATH lcov) find_program(GENHTML_PATH genhtml) # Hide behind the 'advanced' mode flag for GUI/ccmake mark_as_advanced(FORCE LLVM_COV_PATH LLVM_PROFDATA_PATH LCOV_PATH GENHTML_PATH) # Variables set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov) set_property(GLOBAL PROPERTY JOB_POOLS ccov_serial_pool=1) # Common initialization/checks if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED) set(CODE_COVERAGE_ADDED ON) # Common Targets add_custom_target( ccov-preprocessing COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_COVERAGE_OUTPUT_DIRECTORY} DEPENDS ccov-clean) if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") # Messages message(STATUS "Building with llvm Code Coverage Tools") if(NOT LLVM_COV_PATH) message(FATAL_ERROR "llvm-cov not found! Aborting.") else() # Version number checking for 'EXCLUDE' compatibility execute_process(COMMAND ${LLVM_COV_PATH} --version OUTPUT_VARIABLE LLVM_COV_VERSION_CALL_OUTPUT) string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" LLVM_COV_VERSION ${LLVM_COV_VERSION_CALL_OUTPUT}) if(LLVM_COV_VERSION VERSION_LESS "7.0.0") message( WARNING "target_code_coverage()/add_code_coverage_all_targets() 'EXCLUDE' option only available on llvm-cov >= 7.0.0" ) endif() endif() # Targets if(${CMAKE_VERSION} VERSION_LESS "3.17.0") add_custom_target( ccov-clean COMMAND ${CMAKE_COMMAND} -E remove -f ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list COMMAND ${CMAKE_COMMAND} -E remove -f ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) else() add_custom_target( ccov-clean COMMAND ${CMAKE_COMMAND} -E rm -f ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list COMMAND ${CMAKE_COMMAND} -E rm -f ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) endif() # Used to get the shared object file list before doing the main all- # processing add_custom_target( ccov-libs COMMAND ; COMMENT "libs ready for coverage report.") elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") # Messages message(STATUS "Building with lcov Code Coverage Tools") if(CMAKE_BUILD_TYPE) string(TOUPPER ${CMAKE_BUILD_TYPE} upper_build_type) if(NOT ${upper_build_type} STREQUAL "DEBUG") message( WARNING "Code coverage results with an optimized (non-Debug) build may be misleading" ) endif() else() message( WARNING "Code coverage results with an optimized (non-Debug) build may be misleading" ) endif() if(NOT LCOV_PATH) message(FATAL_ERROR "lcov not found! Aborting...") endif() if(NOT GENHTML_PATH) message(FATAL_ERROR "genhtml not found! Aborting...") endif() # Targets add_custom_target(ccov-clean COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters) else() message(FATAL_ERROR "Code coverage requires Clang or GCC. Aborting.") endif() endif() # Adds code coverage instrumentation to a library, or instrumentation/targets # for an executable target. # ~~~ # EXECUTABLE ADDED TARGETS: # GCOV/LCOV: # ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. # ccov-${TARGET_NAME} : Generates HTML code coverage report for the associated named target. # ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. # # LLVM-COV: # ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. # ccov-report : Generates HTML code coverage report for every target added with 'AUTO' parameter. # ccov-${TARGET_NAME} : Generates HTML code coverage report. # ccov-report-${TARGET_NAME} : Prints to command line summary per-file coverage information. # ccov-export-${TARGET_NAME} : Exports the coverage report to a JSON file. # ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information. # ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. # ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line. # ccov-all-export : Exports the coverage report to a JSON file. # # Required: # TARGET_NAME - Name of the target to generate code coverage for. # Optional: # PUBLIC - Sets the visibility for added compile options to targets to PUBLIC instead of the default of PRIVATE. # INTERFACE - Sets the visibility for added compile options to targets to INTERFACE instead of the default of PRIVATE. # PLAIN - Do not set any target visibility (backward compatibility with old cmake projects) # AUTO - Adds the target to the 'ccov' target so that it can be run in a batch with others easily. Effective on executable targets. # ALL - Adds the target to the 'ccov-all' and 'ccov-all-report' targets, which merge several executable targets coverage data to a single report. Effective on executable targets. # EXTERNAL - For GCC's lcov, allows the profiling of 'external' files from the processing directory # COVERAGE_TARGET_NAME - For executables ONLY, changes the outgoing target name so instead of `ccov-${TARGET_NAME}` it becomes `ccov-${COVERAGE_TARGET_NAME}`. # EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! **These do not copy to the 'all' targets.** # OBJECTS - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output # ARGS - For executables ONLY, appends the given arguments to the associated ccov-* executable call # ~~~ function(target_code_coverage TARGET_NAME) # Argument parsing set(options AUTO ALL EXTERNAL PUBLIC INTERFACE PLAIN) set(single_value_keywords COVERAGE_TARGET_NAME) set(multi_value_keywords EXCLUDE OBJECTS ARGS) cmake_parse_arguments( target_code_coverage "${options}" "${single_value_keywords}" "${multi_value_keywords}" ${ARGN}) # Set the visibility of target functions to PUBLIC, INTERFACE or default to # PRIVATE. if(target_code_coverage_PUBLIC) set(TARGET_VISIBILITY PUBLIC) set(TARGET_LINK_VISIBILITY PUBLIC) elseif(target_code_coverage_INTERFACE) set(TARGET_VISIBILITY INTERFACE) set(TARGET_LINK_VISIBILITY INTERFACE) elseif(target_code_coverage_PLAIN) set(TARGET_VISIBILITY PUBLIC) set(TARGET_LINK_VISIBILITY) else() set(TARGET_VISIBILITY PRIVATE) set(TARGET_LINK_VISIBILITY PRIVATE) endif() if(NOT target_code_coverage_COVERAGE_TARGET_NAME) # If a specific name was given, use that instead. set(target_code_coverage_COVERAGE_TARGET_NAME ${TARGET_NAME}) endif() if(CODE_COVERAGE) # Add code coverage instrumentation to the target's linker command if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-instr-generate -fcoverage-mapping) target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-instr-generate -fcoverage-mapping) elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs -ftest-coverage) target_link_libraries(${TARGET_NAME} ${TARGET_LINK_VISIBILITY} gcov) endif() # Targets get_target_property(target_type ${TARGET_NAME} TYPE) # Add shared library to processing for 'all' targets if(target_type STREQUAL "SHARED_LIBRARY" AND target_code_coverage_ALL) if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") add_custom_target( ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} COMMAND ${CMAKE_COMMAND} -E echo "-object=$" >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list DEPENDS ccov-preprocessing ${TARGET_NAME}) if(NOT TARGET ccov-libs) message( FATAL_ERROR "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." ) endif() add_dependencies(ccov-libs ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) endif() endif() # For executables add targets to run and produce output if(target_type STREQUAL "EXECUTABLE") if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") # If there are shared objects to also work with, generate the string to # add them here foreach(SO_TARGET ${target_code_coverage_OBJECTS}) # Check to see if the target is a shared object if(TARGET ${SO_TARGET}) get_target_property(SO_TARGET_TYPE ${SO_TARGET} TYPE) if(${SO_TARGET_TYPE} STREQUAL "SHARED_LIBRARY") set(SO_OBJECTS ${SO_OBJECTS} -object=$) endif() endif() endforeach() # Run the executable, generating raw profile data Make the run data # available for further processing. Separated to allow Windows to run # this target serially. add_custom_target( ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} COMMAND ${CMAKE_COMMAND} -E env LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw $ ${target_code_coverage_ARGS} COMMAND ${CMAKE_COMMAND} -E echo "-object=$" ${SO_OBJECTS} >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list COMMAND ${CMAKE_COMMAND} -E echo "${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw" >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list JOB_POOL ccov_serial_pool DEPENDS ccov-preprocessing ccov-libs ${TARGET_NAME}) # Merge the generated profile data so llvm-cov can process it add_custom_target( ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME} COMMAND ${LLVM_PROFDATA_PATH} merge -sparse ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata DEPENDS ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) # Ignore regex only works on LLVM >= 7 if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) set(EXCLUDE_REGEX ${EXCLUDE_REGEX} -ignore-filename-regex='${EXCLUDE_ITEM}') endforeach() endif() # Print out details of the coverage information to the command line add_custom_target( ccov-show-${target_code_coverage_COVERAGE_TARGET_NAME} COMMAND ${LLVM_COV_PATH} show $ ${SO_OBJECTS} -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata -show-line-counts-or-regions ${EXCLUDE_REGEX} DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) # Print out a summary of the coverage information to the command line add_custom_target( ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME} COMMAND ${LLVM_COV_PATH} report $ ${SO_OBJECTS} -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata ${EXCLUDE_REGEX} DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) # Export coverage information so continuous integration tools (e.g. # Jenkins) can consume it add_custom_target( ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME} COMMAND ${LLVM_COV_PATH} export $ ${SO_OBJECTS} -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata -format="text" ${EXCLUDE_REGEX} > ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) # Generates HTML output of the coverage information for perusal add_custom_target( ccov-${target_code_coverage_COVERAGE_TARGET_NAME} COMMAND ${LLVM_COV_PATH} show $ ${SO_OBJECTS} -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata -show-line-counts-or-regions -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} -format="html" ${EXCLUDE_REGEX} DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info" ) # Run the executable, generating coverage information add_custom_target( ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} COMMAND $ ${target_code_coverage_ARGS} DEPENDS ccov-preprocessing ${TARGET_NAME}) # Generate exclusion string for use foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} '${EXCLUDE_ITEM}') endforeach() if(EXCLUDE_REGEX) set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file ${COVERAGE_INFO}) else() set(EXCLUDE_COMMAND ;) endif() if(NOT ${target_code_coverage_EXTERNAL}) set(EXTERNAL_OPTION --no-external) endif() # Capture coverage data if(${CMAKE_VERSION} VERSION_LESS "3.17.0") add_custom_target( ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters COMMAND $ ${target_code_coverage_ARGS} COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file ${COVERAGE_INFO} COMMAND ${EXCLUDE_COMMAND} DEPENDS ccov-preprocessing ${TARGET_NAME}) else() add_custom_target( ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters COMMAND $ ${target_code_coverage_ARGS} COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file ${COVERAGE_INFO} COMMAND ${EXCLUDE_COMMAND} DEPENDS ccov-preprocessing ${TARGET_NAME}) endif() # Generates HTML output of the coverage information for perusal add_custom_target( ccov-${target_code_coverage_COVERAGE_TARGET_NAME} COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} ${COVERAGE_INFO} DEPENDS ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}) endif() add_custom_command( TARGET ccov-${target_code_coverage_COVERAGE_TARGET_NAME} POST_BUILD COMMAND ; COMMENT "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}/index.html in your browser to view the coverage report." ) # AUTO if(target_code_coverage_AUTO) if(NOT TARGET ccov) add_custom_target(ccov) endif() add_dependencies(ccov ccov-${target_code_coverage_COVERAGE_TARGET_NAME}) if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID MATCHES "GNU") if(NOT TARGET ccov-report) add_custom_target(ccov-report) endif() add_dependencies( ccov-report ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME}) endif() endif() # ALL if(target_code_coverage_ALL) if(NOT TARGET ccov-all-processing) message( FATAL_ERROR "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." ) endif() add_dependencies(ccov-all-processing ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) endif() endif() endif() endfunction() # Adds code coverage instrumentation to all targets in the current directory and # any subdirectories. To add coverage instrumentation to only specific targets, # use `target_code_coverage`. function(add_code_coverage) if(CODE_COVERAGE) if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") add_compile_options(-fprofile-instr-generate -fcoverage-mapping) add_link_options(-fprofile-instr-generate -fcoverage-mapping) elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") add_compile_options(-fprofile-arcs -ftest-coverage) link_libraries(gcov) endif() endif() endfunction() # Adds the 'ccov-all' type targets that calls all targets added via # `target_code_coverage` with the `ALL` parameter, but merges all the coverage # data from them into a single large report instead of the numerous smaller # reports. Also adds the ccov-all-capture Generates an all-merged.info file, for # use with coverage dashboards (e.g. codecov.io, coveralls). # ~~~ # Optional: # EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! # ~~~ function(add_code_coverage_all_targets) # Argument parsing set(multi_value_keywords EXCLUDE) cmake_parse_arguments(add_code_coverage_all_targets "" "" "${multi_value_keywords}" ${ARGN}) if(CODE_COVERAGE) if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") # Merge the profile data for all of the run executables if(WIN32) add_custom_target( ccov-all-processing COMMAND powershell -Command $$FILELIST = Get-Content ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list\; llvm-profdata.exe merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse $$FILELIST) else() add_custom_target( ccov-all-processing COMMAND ${LLVM_PROFDATA_PATH} merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`) endif() # Regex exclude only available for LLVM >= 7 if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) set(EXCLUDE_REGEX ${EXCLUDE_REGEX} -ignore-filename-regex='${EXCLUDE_ITEM}') endforeach() endif() # Print summary of the code coverage information to the command line if(WIN32) add_custom_target( ccov-all-report COMMAND powershell -Command $$FILELIST = Get-Content ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe report $$FILELIST -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata ${EXCLUDE_REGEX} DEPENDS ccov-all-processing) else() add_custom_target( ccov-all-report COMMAND ${LLVM_COV_PATH} report `cat ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata ${EXCLUDE_REGEX} DEPENDS ccov-all-processing) endif() # Export coverage information so continuous integration tools (e.g. # Jenkins) can consume it add_custom_target( ccov-all-export COMMAND ${LLVM_COV_PATH} export `cat ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -format="text" ${EXCLUDE_REGEX} > ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json DEPENDS ccov-all-processing) # Generate HTML output of all added targets for perusal if(WIN32) add_custom_target( ccov-all COMMAND powershell -Command $$FILELIST = Get-Content ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe show $$FILELIST -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -show-line-counts-or-regions -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged -format="html" ${EXCLUDE_REGEX} DEPENDS ccov-all-processing) else() add_custom_target( ccov-all COMMAND ${LLVM_COV_PATH} show `cat ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -show-line-counts-or-regions -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged -format="html" ${EXCLUDE_REGEX} DEPENDS ccov-all-processing) endif() elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info") # Nothing required for gcov add_custom_target(ccov-all-processing COMMAND ;) # Exclusion regex string creation set(EXCLUDE_REGEX) foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} '${EXCLUDE_ITEM}') endforeach() if(EXCLUDE_REGEX) set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file ${COVERAGE_INFO}) else() set(EXCLUDE_COMMAND ;) endif() # Capture coverage data if(${CMAKE_VERSION} VERSION_LESS "3.17.0") add_custom_target( ccov-all-capture COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture --output-file ${COVERAGE_INFO} COMMAND ${EXCLUDE_COMMAND} DEPENDS ccov-preprocessing ccov-all-processing) else() add_custom_target( ccov-all-capture COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture --output-file ${COVERAGE_INFO} COMMAND ${EXCLUDE_COMMAND} DEPENDS ccov-preprocessing ccov-all-processing) endif() # Generates HTML output of all targets for perusal add_custom_target( ccov-all COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged ${COVERAGE_INFO} -p ${CMAKE_SOURCE_DIR} DEPENDS ccov-all-capture) endif() add_custom_command( TARGET ccov-all POST_BUILD COMMAND ; COMMENT "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged/index.html in your browser to view the coverage report." ) endif() endfunction() qcoro-0.12.0/cmake/DetectAndroidNDKVersion.cmake000066400000000000000000000024131477357142500214320ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2023 Daniel Vrátil # # SPDX-License-Identifier: MIT cmake_policy(SET CMP0140 NEW) function(detectAndroidNDKVersion outVar) if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.20) # CMAKE_ANDROID_NDK_VERSION introduced in CMake 3.20 set(${outVar} "${CMAKE_ANDROID_NDK_VERSION}") else() if (NOT CMAKE_ANDROID_NDK) message(STATUS "Couldn't detect Android NDK version: CMAKE_ANDROID_NDK not set") return() endif() if (NOT EXISTS "${CMAKE_ANDROID_NDK}/source.properties") message(STATUS "Couldn't detect Android NDK version: ${CMAKE_ANDROID_NDK}/source.properties doesn't exist") return() endif() file(STRINGS "${CMAKE_ANDROID_NDK}/source.properties" _sources REGEX "^Pkg\.Revision = [0-9]+\.[0-9]+\.[0-9]+$") string(REGEX MATCH "= ([0-9]+\.[0-9]+)\." _match "${_sources}") set(${outVar} "${CMAKE_MATCH_1}") if (NOT ${outVar}) message(STATUS "Couldn't detect Android NDK version: ${CMAKE_ANDROID_NDK}/source.properties doesn't contain Pkg.Revision") return() endif() endif() message(STATUS "Detected Android NDK version ${${outVar}}") return(PROPAGATE ${outVar}) endfunction() qcoro-0.12.0/cmake/ECMGeneratePriFile.cmake000066400000000000000000000254531477357142500203610ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2014 David Faure # # SPDX-License-Identifier: BSD-3-Clause #[=======================================================================[.rst: ECMGeneratePriFile ------------------ Generate a ``.pri`` file for the benefit of qmake-based projects. As well as the function below, this module creates the cache variable ``ECM_MKSPECS_INSTALL_DIR`` and sets the default value to ``mkspecs/modules``. This assumes Qt and the current project are both installed to the same non-system prefix. Packagers who use ``-DCMAKE_INSTALL_PREFIX=/usr`` will certainly want to set ``ECM_MKSPECS_INSTALL_DIR`` to something like ``share/qt5/mkspecs/modules``. The main thing is that this should be the ``modules`` subdirectory of either the default qmake ``mkspecs`` directory or of a directory that will be in the ``$QMAKEPATH`` environment variable when qmake is run. :: ecm_generate_pri_file(BASE_NAME LIB_NAME [VERSION ] # since 5.83 [DEPS " [ [...]]"] [FILENAME_VAR ] [INCLUDE_INSTALL_DIRS [ [...]]] # since 5.92 [INCLUDE_INSTALL_DIR ] # deprecated since 5.92 [LIB_INSTALL_DIR ]) If your CMake project produces a Qt-based library, you may expect there to be applications that wish to use it that use a qmake-based build system, rather than a CMake-based one. Creating a ``.pri`` file will make use of your library convenient for them, in much the same way that CMake config files make things convenient for CMake-based applications. ``ecm_generate_pri_file()`` generates just such a file. VERSION specifies the version of the library the ``.pri`` file describes. If not set, the value is taken from the context variable ``PROJECT_VERSION``. This variable is usually set by the ``project(... VERSION ...)`` command or, if CMake policy CMP0048 is not NEW, by :module:`ECMSetupVersion`. For backward-compatibility with older ECM versions the ``PROJECT_VERSION_STRING`` variable as set by :module:`ECMSetupVersion` will be preferred over ``PROJECT_VERSION`` if set, unless the minimum required version of ECM is 5.83 and newer. Since 5.83. BASE_NAME specifies the name qmake project (.pro) files should use to refer to the library (eg: KArchive). LIB_NAME is the name of the actual library to link to (ie: the first argument to add_library()). DEPS is a space-separated list of the base names of other libraries (for Qt libraries, use the same names you use with the ``QT`` variable in a qmake project file, such as "core" for QtCore). FILENAME_VAR specifies the name of a variable to store the path to the generated file in. INCLUDE_INSTALL_DIRS are the paths (relative to ``CMAKE_INSTALL_PREFIX``) that include files will be installed to. It defaults to ``${INCLUDE_INSTALL_DIR}/`` if the ``INCLUDE_INSTALL_DIR`` variable is set. If that variable is not set, the ``CMAKE_INSTALL_INCLUDEDIR`` variable is used instead, and if neither are set ``include`` is used. LIB_INSTALL_DIR operates similarly for the installation location for libraries; it defaults to ``${LIB_INSTALL_DIR}``, ``${CMAKE_INSTALL_LIBDIR}`` or ``lib``, in that order. INCLUDE_INSTALL_DIR is the old variant of INCLUDE_INSTALL_DIRS, taking only one directory. Example usage: .. code-block:: cmake ecm_generate_pri_file( BASE_NAME KArchive LIB_NAME KF5KArchive DEPS "core" FILENAME_VAR pri_filename VERSION 4.2.0 ) install(FILES ${pri_filename} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) A qmake-based project that wished to use this would then do:: QT += KArchive in their ``.pro`` file. Since pre-1.0.0. #]=======================================================================] # Replicate the logic from KDEInstallDirs.cmake as we can't depend on it # Ask qmake if we're using the same prefix as Qt set(_should_query_qt OFF) if(NOT DEFINED KDE_INSTALL_USE_QT_SYS_PATHS) include(ECMQueryQt) ecm_query_qt(qt_install_prefix_dir QT_INSTALL_PREFIX TRY) if(qt_install_prefix_dir STREQUAL "${CMAKE_INSTALL_PREFIX}") set(_should_query_qt ON) endif() endif() if(KDE_INSTALL_USE_QT_SYS_PATHS OR _should_query_qt) include(ECMQueryQt) ecm_query_qt(qt_install_prefix_dir QT_INSTALL_PREFIX) ecm_query_qt(qt_host_data_dir QT_HOST_DATA) if(qt_install_prefix_dir STREQUAL "${CMAKE_INSTALL_PREFIX}") file(RELATIVE_PATH qt_host_data_dir ${qt_install_prefix_dir} ${qt_host_data_dir}) endif() if(qt_host_data_dir STREQUAL "") set(mkspecs_install_dir mkspecs/modules) else() set(mkspecs_install_dir ${qt_host_data_dir}/mkspecs/modules) endif() set(ECM_MKSPECS_INSTALL_DIR ${mkspecs_install_dir} CACHE PATH "The directory where mkspecs will be installed to.") else() set(ECM_MKSPECS_INSTALL_DIR mkspecs/modules CACHE PATH "The directory where mkspecs will be installed to.") endif() function(ECM_GENERATE_PRI_FILE) set(options INTERFACE) set(oneValueArgs BASE_NAME LIB_NAME DEPS FILENAME_VAR INCLUDE_INSTALL_DIR LIB_INSTALL_DIR VERSION) set(multiValueArgs INCLUDE_INSTALL_DIRS) cmake_parse_arguments(EGPF "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) if(EGPF_UNPARSED_ARGUMENTS) message(FATAL_ERROR "Unknown keywords given to ECM_GENERATE_PRI_FILE(): \"${EGPF_UNPARSED_ARGUMENTS}\"") endif() if(ECM_GLOBAL_FIND_VERSION VERSION_LESS 5.83.0) set(_support_backward_compat_version_string_var TRUE) else() set(_support_backward_compat_version_string_var FALSE) endif() if(NOT EGPF_BASE_NAME) message(FATAL_ERROR "Required argument BASE_NAME missing in ECM_GENERATE_PRI_FILE() call") endif() if(NOT EGPF_LIB_NAME) message(FATAL_ERROR "Required argument LIB_NAME missing in ECM_GENERATE_PRI_FILE() call") endif() if(NOT EGPF_VERSION) if(_support_backward_compat_version_string_var) if(NOT PROJECT_VERSION_STRING AND NOT PROJECT_VERSION) message(FATAL_ERROR "Required variable PROJECT_VERSION_STRING or PROJECT_VERSION not set before ECM_GENERATE_PRI_FILE() call. Missing call of ecm_setup_version() or project(VERSION)?") endif() else() if(NOT PROJECT_VERSION) message(FATAL_ERROR "Required variable PROJECT_VERSION not set before ECM_GENERATE_PRI_FILE() call. Missing call of ecm_setup_version() or project(VERSION)?") endif() endif() endif() if(EGPF_INCLUDE_INSTALL_DIR) if(EGPF_INCLUDE_INSTALL_DIRS) message(FATAL_ERROR "Only one argument of INCLUDE_INSTALL_DIR & INCLUDE_INSTALL_DIRS can be used in ECM_GENERATE_PRI_FILE() call") endif() set(EGPF_INCLUDE_INSTALL_DIRS ${EGPF_INCLUDE_INSTALL_DIR}) endif() if(NOT EGPF_INCLUDE_INSTALL_DIRS) if(INCLUDE_INSTALL_DIR) set(EGPF_INCLUDE_INSTALL_DIRS "${INCLUDE_INSTALL_DIR}/${EGPF_BASE_NAME}") elseif(CMAKE_INSTALL_INCLUDEDIR) set(EGPF_INCLUDE_INSTALL_DIRS "${CMAKE_INSTALL_INCLUDEDIR}/${EGPF_BASE_NAME}") else() set(EGPF_INCLUDE_INSTALL_DIRS "include/${EGPF_BASE_NAME}") endif() endif() if(NOT EGPF_LIB_INSTALL_DIR) if(LIB_INSTALL_DIR) set(EGPF_LIB_INSTALL_DIR "${LIB_INSTALL_DIR}") elseif(CMAKE_INSTALL_LIBDIR) set(EGPF_LIB_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}") else() set(EGPF_LIB_INSTALL_DIR "lib") endif() endif() if(EGPF_VERSION) set(PRI_VERSION "${EGPF_VERSION}") else() if(_support_backward_compat_version_string_var AND PROJECT_VERSION_STRING) set(PRI_VERSION "${PROJECT_VERSION_STRING}") if(NOT PROJECT_VERSION_STRING STREQUAL PROJECT_VERSION) message(DEPRECATION "ECM_GENERATE_PRI_FILE() will no longer support PROJECT_VERSION_STRING when the required minimum version of ECM is 5.83 or newer. Set VERSION parameter or use PROJECT_VERSION instead.") endif() else() set(PRI_VERSION "${PROJECT_VERSION}") endif() endif() string(REGEX REPLACE "^([0-9]+)\\.[0-9]+\\.[0-9]+.*" "\\1" PRI_VERSION_MAJOR "${PRI_VERSION}") string(REGEX REPLACE "^[0-9]+\\.([0-9]+)\\.[0-9]+.*" "\\1" PRI_VERSION_MINOR "${PRI_VERSION}") string(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" PRI_VERSION_PATCH "${PRI_VERSION}") # Prepare the right number of "../.." to go from ECM_MKSPECS_INSTALL_DIR to the install prefix # This allows to make the generated pri files relocatable (no absolute paths) if (IS_ABSOLUTE ${ECM_MKSPECS_INSTALL_DIR}) set(BASEPATH ${CMAKE_INSTALL_PREFIX}) else() string(REGEX REPLACE "[^/]+" ".." PRI_ROOT_RELATIVE_TO_MKSPECS ${ECM_MKSPECS_INSTALL_DIR}) set(BASEPATH "$$PWD/${PRI_ROOT_RELATIVE_TO_MKSPECS}") endif() set(PRI_TARGET_BASENAME ${EGPF_BASE_NAME}) set(PRI_TARGET_LIBNAME ${EGPF_LIB_NAME}) set(PRI_TARGET_QTDEPS ${EGPF_DEPS}) set(PRI_TARGET_INCLUDES) foreach(_dir ${EGPF_INCLUDE_INSTALL_DIRS}) # separate list entries with space if(IS_ABSOLUTE "${_dir}") string(APPEND PRI_TARGET_INCLUDES " ${_dir}") else() string(APPEND PRI_TARGET_INCLUDES " ${BASEPATH}/${_dir}") endif() endforeach() if(IS_ABSOLUTE "${EGPF_LIB_INSTALL_DIR}") set(PRI_TARGET_LIBS "${EGPF_LIB_INSTALL_DIR}") else() set(PRI_TARGET_LIBS "${BASEPATH}/${EGPF_LIB_INSTALL_DIR}") endif() set(PRI_TARGET_DEFINES "") set(PRI_FILENAME ${CMAKE_CURRENT_BINARY_DIR}/qt_${PRI_TARGET_BASENAME}.pri) if (EGPF_FILENAME_VAR) set(${EGPF_FILENAME_VAR} ${PRI_FILENAME} PARENT_SCOPE) endif() set(PRI_TARGET_MODULE_CONFIG "") # backward compat: it was not obvious LIB_NAME needs to be a target name, # and some projects where the target name was not the actual library output name # passed the output name for LIB_NAME, so .name & .module prperties are correctly set. # TODO: improve API dox, allow control over module name if target name != output name if(TARGET ${EGPF_LIB_NAME}) get_target_property(target_type ${EGPF_LIB_NAME} TYPE) if (target_type STREQUAL "STATIC_LIBRARY") set(PRI_TARGET_MODULE_CONFIG "staticlib") endif() endif() if (EGPF_INTERFACE) set(PRI_TARGET_MODULE "") else() set(PRI_TARGET_MODULE "${PRI_TARGET_LIBNAME}") endif() file(GENERATE OUTPUT ${PRI_FILENAME} CONTENT "QT.${PRI_TARGET_BASENAME}.VERSION = ${PRI_VERSION} QT.${PRI_TARGET_BASENAME}.MAJOR_VERSION = ${PRI_VERSION_MAJOR} QT.${PRI_TARGET_BASENAME}.MINOR_VERSION = ${PRI_VERSION_MINOR} QT.${PRI_TARGET_BASENAME}.PATCH_VERSION = ${PRI_VERSION_PATCH} QT.${PRI_TARGET_BASENAME}.name = ${PRI_TARGET_LIBNAME} QT.${PRI_TARGET_BASENAME}.module = ${PRI_TARGET_MODULE} QT.${PRI_TARGET_BASENAME}.defines = ${PRI_TARGET_DEFINES} QT.${PRI_TARGET_BASENAME}.includes = ${PRI_TARGET_INCLUDES} QT.${PRI_TARGET_BASENAME}.private_includes = QT.${PRI_TARGET_BASENAME}.libs = ${PRI_TARGET_LIBS} QT.${PRI_TARGET_BASENAME}.depends = ${PRI_TARGET_QTDEPS} QT.${PRI_TARGET_BASENAME}.module_config = ${PRI_TARGET_MODULE_CONFIG} " ) endfunction() qcoro-0.12.0/cmake/ECMQueryQt.cmake000066400000000000000000000060761477357142500167660ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2014 Rohan Garg # SPDX-FileCopyrightText: 2014 Alex Merry # SPDX-FileCopyrightText: 2014-2016 Aleix Pol # SPDX-FileCopyrightText: 2017 Friedrich W. H. Kossebau # SPDX-FileCopyrightText: 2022 Ahmad Samir # # SPDX-License-Identifier: BSD-3-Clause #[=======================================================================[.rst: ECMQueryQt --------------- This module can be used to query the installation paths used by Qt. For Qt5 this uses ``qmake``, and for Qt6 this used ``qtpaths`` (the latter has built-in support to query the paths of a target platform when cross-compiling). This module defines the following function: :: ecm_query_qt( [TRY]) Passing ``TRY`` will result in the method not making the build fail if the executable used for querying has not been found, but instead simply print a warning message and return an empty string. Example usage: .. code-block:: cmake include(ECMQueryQt) ecm_query_qt(bin_dir QT_INSTALL_BINS) If the call succeeds ``${bin_dir}`` will be set to ``/path/to/bin/dir`` (e.g. ``/usr/lib64/qt/bin/``). Since: 5.93 #]=======================================================================] if (QT_VERSION_MAJOR STREQUAL "5") find_package(Qt${QT_VERSION_MAJOR}Core QUIET) get_target_property(_qmake_executable_default Qt5::qmake LOCATION) set(QUERY_EXECUTABLE ${_qmake_executable_default} CACHE FILEPATH "Location of the Qt5 qmake executable") set(_exec_name_text "Qt5 qmake") set(_cli_option "-query") elseif(QT_VERSION_MAJOR STREQUAL "6") find_package(Qt6 COMPONENTS CoreTools REQUIRED CONFIG) get_target_property(_qtpaths_executable Qt6::qtpaths LOCATION) set(QUERY_EXECUTABLE ${_qtpaths_executable} CACHE FILEPATH "Location of the Qt6 qtpaths executable") set(_exec_name_text "Qt6 qtpaths") set(_cli_option "--query") endif() function(ecm_query_qt result_variable qt_variable) set(options TRY) set(oneValueArgs) set(multiValueArgs) cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) if(NOT QUERY_EXECUTABLE) if(ARGS_TRY) set(${result_variable} "" PARENT_SCOPE) message(STATUS "No ${_exec_name_text} executable found. Can't check ${qt_variable}") return() else() message(FATAL_ERROR "No ${_exec_name_text} executable found. Can't check ${qt_variable} as required") endif() endif() execute_process( COMMAND ${QUERY_EXECUTABLE} ${_cli_option} "${qt_variable}" RESULT_VARIABLE return_code OUTPUT_VARIABLE output ) if(return_code EQUAL 0) string(STRIP "${output}" output) file(TO_CMAKE_PATH "${output}" output_path) set(${result_variable} "${output_path}" PARENT_SCOPE) else() message(WARNING "Failed call: ${_command} \"${qt_variable}\"") message(FATAL_ERROR "${_exec_name_text} call failed: ${return_code}") endif() endfunction() qcoro-0.12.0/cmake/GenerateHeaders.cmake000066400000000000000000000022641477357142500200500ustar00rootroot00000000000000function(generate_headers output_var) set(options) set(oneValueArgs OUTPUT_DIR ORIGINAL_PREFIX ORIGINAL_HEADERS_VAR) set(multiValueArgs HEADER_NAMES) cmake_parse_arguments(GH "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) if (GH_OUTPUT_DIR) set(GH_OUTPUT_DIR "${GH_OUTPUT_DIR}/") endif() foreach(_headername ${GH_HEADER_NAMES}) string(TOLOWER "${_headername}" originalbase) set(CC_ORIGINAL_FILE "${CMAKE_CURRENT_SOURCE_DIR}/${GH_ORIGINAL_PREFIX}/${originalbase}.h") if (NOT EXISTS ${CC_ORIGINAL_FILE}) message(FATAL_ERROR "Could not find header \"${CC_ORIGINAL_FILE}\"") endif() set(CC_HEADER_FILE "${CMAKE_CURRENT_BINARY_DIR}/${GH_OUTPUT_DIR}/${_headername}") if (NOT EXISTS ${CC_HEADER_FILE}) file(WRITE ${CC_HEADER_FILE} "#include \"${GH_ORIGINAL_PREFIX}${originalbase}.h\"") endif() list(APPEND ${output_var} ${CC_HEADER_FILE}) list(APPEND ${GH_ORIGINAL_HEADERS_VAR} ${CC_ORIGINAL_FILE}) endforeach() set(${output_var} ${${output_var}} PARENT_SCOPE) set(${GH_ORIGINAL_HEADERS_VAR} ${${GH_ORIGINAL_HEADERS_VAR}} PARENT_SCOPE) endfunction() qcoro-0.12.0/cmake/GenerateModuleConfigFile.cmake000066400000000000000000000035411477357142500216470ustar00rootroot00000000000000include(CMakePackageConfigHelpers) function(generate_cmake_module_config_file) function(process_dependencies) set(oneValueArgs PREFIX OUTPUT) set(multiValueArgs LIBRARIES) cmake_parse_arguments(deps "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) set(_deps) set(_deps_private FALSE) foreach(dep ${deps_LIBRARIES}) if ("${dep}" MATCHES "PUBLIC|INTERFACE") set(_deps_private FALSE) continue() elseif ("${dep}" STREQUAL "PRIVATE") set(_deps_private TRUE) continue() endif() if (NOT _deps_private) set(_deps "${_deps}find_dependency(${deps_PREFIX}${dep})\n") endif() endforeach() set(${deps_OUTPUT} ${_deps} PARENT_SCOPE) endfunction() set(options) set(oneValueArgs TARGET_NAME NAME) set(multiValueArgs QT_DEPENDENCIES QCORO_DEPENDENCIES) cmake_parse_arguments(cmc "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) process_dependencies( PREFIX "Qt${QT_VERSION_MAJOR}" LIBRARIES ${cmc_QT_DEPENDENCIES} OUTPUT QT_DEPENDENCIES ) process_dependencies( PREFIX "QCoro${QT_VERSION_MAJOR}" LIBRARIES ${cmc_QCORO_DEPENDENCIES} OUTPUT QCORO_DEPENDENCIES ) set(MODULE_NAME "${cmc_NAME}") configure_package_config_file( "${qcoro_SOURCE_DIR}/cmake/QCoroModuleConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/${cmc_TARGET_NAME}Config.cmake" INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${cmc_TARGET_NAME} PATH_VARS CMAKE_INSTALL_INCLUDEDIR ) write_basic_package_version_file( "${CMAKE_CURRENT_BINARY_DIR}/${cmc_TARGET_NAME}ConfigVersion.cmake" VERSION ${qcoro_VERSION} COMPATIBILITY SameMajorVersion ) endfunction() qcoro-0.12.0/cmake/QCoroFindQt.cmake000066400000000000000000000023521477357142500171510ustar00rootroot00000000000000macro(qcoro_find_qt) set(options) set(oneValueArgs QT_VERSION FOUND_VER_VAR) set(multiValueArgs COMPONENTS QT5_COMPONENTS QT6_COMPONENTS) cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) if (NOT ARGS_QT_VERSION) find_package(Qt6Core QUIET) if (Qt6Core_FOUND) set(ARGS_QT_VERSION 6) else() set(ARGS_QT_VERSION 5) endif() endif() list(APPEND REQUIRED_QT_COMPONENTS "${ARGS_QT${ARGS_QT_VERSION}_COMPONENTS}") list(FILTER REQUIRED_QT_COMPONENTS EXCLUDE REGEX "Private$$") find_package(Qt${ARGS_QT_VERSION} REQUIRED COMPONENTS ${REQUIRED_QT_COMPONENTS}) if ("${ARGS_QT_VERSION}" STREQUAL "6" AND Qt6_VERSION VERSION_GREATER_EQUAL "6.10.0") list(APPEND REQUIRED_PRIVATE_QT_COMPONENTS "${ARGS_COMPONENTS}") list(APPEND REQUIRED_PRIVATE_QT_COMPONENTS "${ARGS_QT${ARGS_QT_VERSION}_COMPONENTS}") list(FILTER REQUIRED_PRIVATE_QT_COMPONENTS INCLUDE REGEX "Private$$") if (REQUIRED_PRIVATE_QT_COMPONENTS) find_package(Qt${ARGS_QT_VERSION} REQUIRED COMPONENTS ${REQUIRED_PRIVATE_QT_COMPONENTS}) endif() endif() set(${ARGS_FOUND_VER_VAR} ${ARGS_QT_VERSION}) endmacro() qcoro-0.12.0/cmake/QCoroModuleConfig.cmake.in000066400000000000000000000010041477357142500207350ustar00rootroot00000000000000@PACKAGE_INIT@ include(CMakeFindDependencyMacro) @QT_DEPENDENCIES@ @QCORO_DEPENDENCIES@ include("${CMAKE_CURRENT_LIST_DIR}/QCoro@QT_VERSION_MAJOR@@MODULE_NAME@Targets.cmake") # Versionless target, for compatiblity with Qt6 if (TARGET QCoro@QT_VERSION_MAJOR@::@MODULE_NAME@ AND NOT TARGET QCoro::@MODULE_NAME@) add_library(QCoro::@MODULE_NAME@ INTERFACE IMPORTED) set_target_properties(QCoro::@MODULE_NAME@ PROPERTIES INTERFACE_LINK_LIBRARIES "QCoro@QT_VERSION_MAJOR@::@MODULE_NAME@" ) endif() qcoro-0.12.0/config.cmake.in000066400000000000000000000000451477357142500156070ustar00rootroot00000000000000#cmakedefine QCORO_QT_HAS_COMPAT_ABI qcoro-0.12.0/docker/000077500000000000000000000000001477357142500142035ustar00rootroot00000000000000qcoro-0.12.0/docker/Dockerfile.clang000066400000000000000000000054431477357142500172660ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2022 Daniel Vrátil # SPDX-License-Identifier: MIT # # Docker image for specific versions of clang and specific version of Qt. FROM debian:bullseye ARG compiler_version ARG qt_version ARG qt_modules ARG qt_archives SHELL ["/bin/bash", "-c"] # Install build & runtime dependencies RUN apt-get update \ && apt-get upgrade --yes \ && apt-get install --yes --no-install-recommends \ cmake=3.18.4\* cmake-data=3.18.4\* \ make \ python3-pip python3-setuptools python3-wheel python3-dev \ dbus dbus-x11 \ libglib2.0-dev libxkbcommon-dev libfreetype6-dev libfontconfig1-dev \ libssl-dev \ libegl-dev libgl-dev libegl1=1.3.2\* libgl1=1.3.2\* libglx0=1.3.2\* libglvnd0=1.3.2\* \ libvulkan-dev # Install and set up clang RUN \ # Set up env \ if [ "${compiler_version}" == "dev" ]; then \ export CLANG_VERSION_SUFFIX=""; \ else \ export CLANG_VERSION_SUFFIX="-${compiler_version}"; \ fi && \ echo "export CC=\"/usr/bin/clang${CLANG_VERSION_SUFFIX}\"" >> /etc/profile.d/clang.sh && \ echo "export CXX=\"/usr/bin/clang++${CLANG_VERSION_SUFFIX}\"" >> /etc/profile.d/clang.sh && \ # Install tools needed to add the clang repository \ apt-get install --yes --no-install-recommends ca-certificates wget gnupg && \ # Add clang repository \ echo "deb http://apt.llvm.org/bullseye/ llvm-toolchain-bullseye${CLANG_VERSION_SUFFIX} main" > /etc/apt/sources.list.d/llvm.list && \ echo "deb-src http://apt.llvm.org/bullseye/ llvm-toolchain-bullseye${CLANG_VERSION_SUFFIX} main" >> /etc/apt/sources.list.d/llvm.list && \ wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - && \ # Install clang \ apt-get update && \ apt-get install --yes --install-recommends clang${CLANG_VERSION_SUFFIX} && \ clang_major_version=$(clang${CLANG_VERSION_SUFFIX} --version | grep -Eo "[0-9]+.[0-9]+.[0-9]+" | cut -d'.' -f1) && \ if [ ${clang_major_version} -gt 11 ]; then \ apt-get install --yes --no-install-recommends libclang-rt-${clang_major_version}-dev; \ fi # Workaround a bug in CMake (?) which tries to look for OpenGL-related libraries # in /usr/lib/x86_64-unknown-linux-gnu instead of /usr/lib/x86_64-linux-gnu RUN ln -s x86_64-linux-gnu /usr/lib/x86_64-unknown-linux-gnu # Install Qt WORKDIR /root RUN pip3 install 'aqtinstall==3.2.0' COPY install-qt.sh ./install-qt.sh RUN ./install-qt.sh "${qt_version}" "${qt_modules}" "${qt_archives}" # Set Qt up environment ENV QT_BASE_DIR "/opt/qt/${qt_version}/gcc_64" ENV PATH "${QT_BASE_DIR}/bin:${PATH}" ENV CMAKE_PREFIX_PATH "${QT_BASE_DIR}/lib/cmake" ENV LD_LIBRARY_PATH "${QT_BASE_DIR}/lib:${LD_LIBRARY_PATH}" ENV XDG_DATA_DIRS "${QT_BASE_DIR}/share:${XDG_DATA_DIRS}" ENTRYPOINT [ "/bin/bash", "-l" ] qcoro-0.12.0/docker/Dockerfile.gcc000066400000000000000000000035501477357142500167330ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2022 Daniel Vrátil # SPDX-License-Identifier: MIT # # Docker image for specific versions of gcc and specific version of Qt. ARG compiler_version FROM gcc:${compiler_version} ARG qt_version ARG qt_modules ARG qt_archives SHELL ["/bin/bash", "-c"] # Enable backports in older GCC images to get access to newer cmake RUN source /etc/os-release && \ if [[ "${VERSION_ID}" == "10" ]]; then \ echo "deb http://deb.debian.org/debian buster-backports main" > /etc/apt/sources.list.d/backports.list; \ fi # Install build-time and runtime dependencies RUN apt-get update \ && apt-get upgrade --yes \ && apt-get install --yes --no-install-recommends \ cmake cmake-data \ python3-pip python3-setuptools python3-wheel python3-dev \ dbus dbus-x11 \ libglib2.0-dev libxkbcommon-dev libfreetype6-dev libfontconfig1-dev \ libssl-dev \ libegl-dev libgl-dev libegl1 libgl1 libglx0 libglvnd0 \ libvulkan-dev \ && apt-get clean # Workaround a bug in CMake (?) which tries to look for OpenGL-related libraries # in /usr/lib/x86_64-unknown-linux-gnu instead of /usr/lib/x86_64-linux-gnu RUN ln -s x86_64-linux-gnu /usr/lib/x86_64-unknown-linux-gnu # Install Qt WORKDIR /root # Necessary to allow using pip on newer images (--break-system-packages doesn't work on older images) RUN rm -f /usr/lib/python$(python3 --version | grep -oE "3.[0-9]+")/EXTERNALLY-MANAGED \ && pip3 install 'aqtinstall==3.2.0' COPY install-qt.sh ./install-qt.sh RUN ./install-qt.sh "${qt_version}" "${qt_modules}" "${qt_archives}" # Set up environment ENV QT_BASE_DIR "/opt/qt/${qt_version}/gcc_64" ENV PATH "${QT_BASE_DIR}/bin:${PATH}" ENV CMAKE_PREFIX_PATH="${QT_BASE_DIR}/lib/cmake" ENV LD_LIBRARY_PATH "${QT_BASE_DIR}/lib:${LD_LIBRARY_PATH}" ENV XDG_DATA_DIRS "${QT_BASE_DIR}/share:${XDG_DATA_DIRS}" qcoro-0.12.0/docker/install-qt.sh000077500000000000000000000006661477357142500166420ustar00rootroot00000000000000#!/bin/bash set -e qt_version="$1" qt_modules="$2" qt_archives="$3" if [[ -n "${qt_archives}" ]]; then opt_archives="--archives ${qt_archives}" fi if [[ -n "${qt_modules}" ]]; then opt_modules="--modules ${qt_modules}" fi echo "Installing Qt ${qt_version}" echo "Modules: ${qt_modules}" echo "Archives: ${qt_archives}" aqt install-qt -O /opt/qt linux desktop "${qt_version}" gcc_64 ${opt_archives} ${opt_modules} echo "Done." qcoro-0.12.0/docs/000077500000000000000000000000001477357142500136645ustar00rootroot00000000000000qcoro-0.12.0/docs/about/000077500000000000000000000000001477357142500147765ustar00rootroot00000000000000qcoro-0.12.0/docs/about/license.md000066400000000000000000000023621477357142500167450ustar00rootroot00000000000000 # License --- QCoro is published under the MIT License ## MIT License Copyright (c) 2021 Daniel Vrátil Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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 AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. qcoro-0.12.0/docs/assets/000077500000000000000000000000001477357142500151665ustar00rootroot00000000000000qcoro-0.12.0/docs/assets/qcoro.svg000066400000000000000000000226111477357142500170340ustar00rootroot00000000000000qcoro-0.12.0/docs/building-and-using.md000066400000000000000000000052771477357142500177010ustar00rootroot00000000000000 # Building and Using QCoro ## Building QCoro QCoro uses CMake build system. You can pass following options to the `cmake` command when building QCoro to customize the build: * `-DQCORO_BUILD_EXAMPLES` - whether to build examples or not (`ON` by default). * `-DQCORO_BUILD_TESTING` - whether to build tests or not (defaults to `${BUILD_TESTING}`), can be used to disable building QCoro tests when building QCoro as part of a bigger project which has `BUILD_TESTING` enabled. * `-DQCORO_ENABLE_ASAN` - whether to build QCoro with AddressSanitizer (`OFF` by default). * `-DBUILD_SHARED_LIBS` - whether to build QCoro as a shared library (`OFF` by default). * `-DUSE_QT_VERSION` - set to `5` or `6` to force a particular version of Qt. When not set the highest available version is used. * `-DQCORO_WITH_QTDBUS` - whether to compile support for QtDBus (`ON` by default). * `-DQCORO_WITH_QTNETWORK` - whether to compile support for QtNetwork (`ON` by default). * `-DQCORO_WITH_QTWEBSOCKETS` - whether to compile support for QtWebSockets (`ON` by default). * `-DQCORO_DISABLE_DEPRECATED_TASK_H` - will not build and install the deprecated task.h header (`OFF` by default). ``` mkdir build cd build cmake .. make # This will install QCoro into /usr/local/ prefix, change it by passing -DCMAKE_INSTALL_PREFIX=/usr # to the cmake command above. sudo make install ``` ## CMake Depending on whether you want to use Qt5 or Qt6 build of QCoro, you should use `QCoro5` or QCoro6` in your CMake code, respectively. The example below is assuming Qt6: ```cmake # Use QCoro5 if you are building for Qt5! find_package(QCoro6 REQUIRED COMPONENTS Core Network DBus) # Set necessary compiler flags to enable coroutine support qcoro_enable_coroutines() ... target_link_libraries(your-target QCoro::Core QCoro::Network QCoro::DBus) ``` Note the missing Qt version number in the `QCoro` target namespace: QCoro provides both versioned (`QCoro5` and `QCoro6`) namespaces as well as version-less namespace, which is especially useful for transitioning codebase from Qt5 to Qt6. ## QMake Using QCoro with QMake projects (`.pro`) is simple: just add the required QCoro modules to the `QT` variable: ``` QT += QCoroCore QCoroNetwork QCoroDBus # Enable C++20 CONFIG += c+=20 # Enable coroutine support in the compiler QMAKE_CXXFLAGS += -fcoroutines ``` You don't need to worry about Qt5 vs Qt6, qmake will pick up the correct build of QCoro depending on whether you are using QMake for Qt5 or Qt6. Currently it's necessary to manually enable C++20 and coroutine support (unless that's already default in your system/compiler settings). qcoro-0.12.0/docs/changelog.md000066400000000000000000000041551477357142500161420ustar00rootroot00000000000000--- title: Changelog --- # Changelog ## 0.11.0 (2024-10-04) * [Release announcement](news/2024/2024-10-04-qcoro-0.11.0-announcement.md) * [Github release changelog](https://github.com/danvratil/qcoro/releases/tag/v0.11.0) ## 0.10.0 (2023-12-05) * [Release announcement](news/2023/2023-12-05-qcoro-0.10.0-announcement.md) * [Github release changelog](https://github.com/danvratil/qcoro/releases/tag/v0.10.0) ## 0.9.0 (2023-04-27) * [Release announcement](news/2023/2023-04-27-qcoro-0.9.0-announcement.md) * [Github release changelog](https://github.com/danvratil/qcoro/releases/tag/v0.9.0) ## 0.8.0 (2023-01-31) * [Release announcement](news/2023/2023-01-31-qcoro-0.8.0-announcement.md) * [Github release changelog](https://github.com/danvratil/qcoro/releases/tag/v0.8.0) ## 0.7.0 (2022-11-20) * [Release announcement](news/2022/2022-11-17-qcoro-0.7.0-announcement.md) * [Github release changelog](https://github.com/danvratil/qcoro/releases/tag/v0.7.0) ## 0.6.0 (2022-07-09) * [Release announcement](news/2022/2022-07-09-qcoro-0.6.0-announcement.md) * [Github release changelog](https://github.com/danvratil/qcoro/releases/tag/v0.6.0) ## 0.5.1 (2022-04-27) * [Github release changelog](https://github.com/danvratil/qcoro/releases/tag/v0.5.1) ## 0.5.0 (2022-04-25) * [Release announcement](news/2022/2022-04-25-qcoro-0.5.0-announcement.md) * [Github release changelog](https://github.com/danvratil/qcoro/releases/tag/v0.5.0) ## 0.4.0 (2022-01-06) * [Release announcement](news/2022/2022-01-06-qcoro-0.4.0-announcement.md) * [Github release changelog](https://github.com/danvratil/qcoro/releases/tag/v0.4.0) ## 0.3.0 (2021-10-11) * [Github release changelog](https://github.com/danvratil/qcoro/releases/tag/v0.3.0) ## 0.2.0 (2021-09-08) * [Release announcement](news/2021/2021-09-08-qcoro-0.2.0-announcement.md) * [Github release changelog](https://github.com/danvratil/qcoro/releases/tag/v0.2.0) ## 0.1.0 (2021-08-15) * Initial release QCoro * [Release announcement](news/2021/2021-08-16-qcoro-0.1.0-announcement.md) qcoro-0.12.0/docs/coroutines/000077500000000000000000000000001477357142500160565ustar00rootroot00000000000000qcoro-0.12.0/docs/coroutines/coawait.md000066400000000000000000000046161477357142500200360ustar00rootroot00000000000000 # `co_await` Explained The following paragraphs try to explain what is a coroutine and what `co_await` does in some simple way. I don't guarantee that any of this is factically correct. For more gritty (and correct) details, refer to the articles linked at the bottom of this document. Coroutines, simply put, are like normal functions except that they can be suspended (and resumed) in the middle. When a coroutine is suspended, execution returns to the function that has called the coroutine. If that function is also a coroutine and is waiting (`co_await`ing) for the current coroutine to finish, then it is suspended as well and the execution returns to the function that has called that coroutine and so on, until a function that is an actual function (not a coroutine) is reached. In case of a regular Qt program, this "top-level" non-coroutine function will be the Qt's event loop - which means that while your coroutine, when called from the Qt event loop is suspended, the Qt event loop will continue to run until the coroutine is resumed again. Amongst many other things, this allows you to write asynchronous code as if it were synchronous without blocking the Qt event loop and making your application unresponsive. See the different examples in this document. Now let's look at the `co_await` keyword. This keyword tells the compiler that this is the point where the coroutine wants to be suspended, until the *awaited* object (the *awaitable*) is ready. Anything type can be *awaitable* - either because it directly implements the interface needed by the C++ coroutine machinery, or because some external tools (like this library) are provided to wrap that type into something that implements the *awaitable* interface. The C++ coroutines introduce two additional keywords -`co_return` and `co_yield`: From an application programmer point of view, `co_return` behaves exactly the same as `return`, except that you cannot use the regular `return` in coroutines. There are some major differences under the hood, though, which is likely why there's a special keyword for returning from coroutines. `co_yield` allows a coroutine to produce a result without actually returning. Can be used for writing generators. Currently, this library has no support/usage of `co_yield`, so I won't go into more details here. qcoro-0.12.0/docs/coroutines/qt-vs-coawait.md000066400000000000000000000027541477357142500211070ustar00rootroot00000000000000 # Qt vs. co_await One of the best examples where coroutines simplify your code is when dealing with asynchronous operations, like network operations. Let's see how a simple HTTP request would be handled in Qt using the signals/slots mechanism: ```cpp void MyClass::fetchData() { auto *nam = new QNetworkAccessManager(this); auto *reply = nam->get(QUrl{QStringLiteral("https://.../api/fetch")}); QObject::connect(reply, &QNetworkReply::finished, [reply, nam]() { const auto data = reply->readAll(); doSomethingWithData(data); reply->deleteLater(); nam->deleteLater(); }); } ``` Now let's see how the code looks like if we use coroutines: ```cpp QCoro::Task<> MyClass::fetchData() { QNetworkReply nam; auto *reply = co_await nam.get(QUrl{QStringLiteral("https://.../api/fetch")}); const auto data = reply->readAll(); reply->deleteLater(); doSomethingWithData(data); } ``` The magic here is the `co_await` keyword which has turned our method `fetchData()` into a coroutine and suspended its execution while the network request was running. When the request finishes, the coroutine is resumed from where it was suspended and continues. And the best part? While the coroutine is suspended, the Qt event loop runs as usual! qcoro-0.12.0/docs/coroutines/reading.md000066400000000000000000000016261477357142500200160ustar00rootroot00000000000000 # More reading This library is inspired by Lewis Bakers' cppcoro library, which also served as a guide to implementing the coroutine machinery, alongside his great series on C++ coroutines: * [Coroutine Theory](https://lewissbaker.github.io/2017/09/25/coroutine-theory) * [Understanding Operator co_await](https://lewissbaker.github.io/2017/11/17/understanding-operator-co-await) * [Understanding the promise type](https://lewissbaker.github.io/2018/09/05/understanding-the-promise-type) * [Understanding Symmetric Transfer](https://lewissbaker.github.io/2020/05/11/understanding_symmetric_transfer) I can also recommend numerous articles about C++ coroutines by [Raymond Chen on his blog OldNewThink][oldnewthing]. [oldnewthing]: https://devblogs.microsoft.com/oldnewthing/author/oldnewthing qcoro-0.12.0/docs/examples/000077500000000000000000000000001477357142500155025ustar00rootroot00000000000000qcoro-0.12.0/docs/examples/qdbus.cpp000066400000000000000000000026751477357142500173360ustar00rootroot00000000000000#include QCoro::Task PlayerControl::nextSong() { // Create a regular QDBusInterface representing the Spotify MPRIS interface QDBusInterface spotifyPlayer{QStringLiteral("org.mpris.MediaPlayer2.spotify"), QStringLiteral("/org/mpris/MediaPlayer2"), QStringLiteral("org.mpris.MediaPlayer2.Player")}; // Call CanGoNext DBus method and co_await reply. During that the current coroutine is suspended. const QDBusReply canGoNext = co_await spotifyPlayer.asyncCall(QStringLiteral("CanGoNext")); // Response has arrived and coroutine is resumed. If the player can go to the next song, // do another async call to do so. if (static_cast(canGoNext)) { // co_await the call to finish, but throw away the result co_await spotifyPlayer.asyncCall(QStringLiteral("Next")); } // Finally, another async call to retrieve new track metadata. Once again, the coroutine // is suspended while we wait for the result. const QDBusReply metadata = co_await spotifyPlayer.asyncCall(QStringLiteral("Metadata")); // Since this function uses co_await, it is in fact a coroutine, so it must use co_return in order // to return our result. By definition, the result of this function can be co_awaited by the caller. co_return static_cast(metadata)[QStringLiteral("xesam:title")].toString(); } qcoro-0.12.0/docs/examples/qfuture.cpp000066400000000000000000000010421477357142500176760ustar00rootroot00000000000000#include QCoro::Task<> runTask() { // Starts a concurrent task and co_awaits on the returned QFuture. While the task is // running, the coroutine is suspended. const QString value = co_await QtConcurrent::run([]() { QString result; ... // do some long-running computation ... return result; }); // When the future has finished, the coroutine is resumed and the result of the // QFuture is returned and stored in `value`. // ... now do something with the value } qcoro-0.12.0/docs/examples/qnetworkreply.cpp000066400000000000000000000013441477357142500211360ustar00rootroot00000000000000#include QCoro::Task<> MyClass::fetchData() { // Creates QNetworkAccessManager on stack QNetworkAccessManager nam; // Calls QNetworkAccessManager::get() and co_awaits on the returned QNetworkReply* // until it finishes. The current coroutine is suspended until that. auto *reply = co_await nam.get(QUrl{QStringLiteral("https://.../api/fetch")}); // When the reply finishes, the coroutine is resumed and we can access the reply content. const auto data = reply->readAll(); // Raise your hand if you never forgot to delete a QNetworkReply... delete reply; doSomethingWithData(data); // Extra bonus: the QNetworkAccessManager is destroyed automatically, since it's on stack. } qcoro-0.12.0/docs/examples/qprocess.cpp000066400000000000000000000006261477357142500200510ustar00rootroot00000000000000#include QCoro::Task listDir(const QString &dirPath) { QProcess basicProcess; auto process = qCoro(basicProcess); qDebug() << "Starting ls..."; co_await process.start(QStringLiteral("/bin/ls"), {dirPath}); qDebug() << "Ls started, reading directory..."; co_await process.waitForFinished(); qDebug() << "Done"; return basicProcess.readAll(); } qcoro-0.12.0/docs/examples/qtcpserver.cpp000066400000000000000000000005271477357142500204100ustar00rootroot00000000000000#include QCoro::Task<> runServer(uint16_t port) { QTcpServer server; server.listen(QHostAddress::LocalHost, port); while (server.isListening()) { auto *socket = co_await qCoro(server).waitForNewConnection(10s); if (socket != nullptr) { newClientConnection(socket); } } } qcoro-0.12.0/docs/examples/qtcpsocket.cpp000066400000000000000000000007121477357142500203660ustar00rootroot00000000000000#include QCoro::Task requestDataFromServer(const QString &hostName) { QTcpSocket socket; if (!co_await qCoro(socket).connectToHost(hostName)) { qWarning() << "Failed to connect to the server"; co_return QByteArray{}; } socket.write("SEND ME DATA!"); QByteArray data; while (!data.endsWith("\r\n.\r\n")) { data += co_await qCoro(socket).readAll(); } co_return data; } ` qcoro-0.12.0/docs/examples/qthread.cpp000066400000000000000000000010041477357142500176310ustar00rootroot00000000000000#include #include #include QCoro::Task MainWindow::processData(const QVector &data) { std::unique_ptr thread(QThread::create([data]() { // Perform some intesive calculation })); thread->start(); ui->setState(tr("Processing is starting...")); co_await qCoro(thread.get()).waitForStarted(); ui->setState(tr("Processing data...")); co_await qCoro(thread.get()).waitForFinished(); ui->setState(tr("Processing done.")); } qcoro-0.12.0/docs/index.md000066400000000000000000000102631477357142500153170ustar00rootroot00000000000000 # QCoro C++ Coroutine Library for Qt5 and Qt6 --- ## Overview QCoro is a C++ library that provide set of tools to make use of C++20 coroutines in connection with certain asynchronous Qt actions. Take a look at the example below to see what an amazing thing coroutines are: ```cpp QNetworkAccessManager networkAccessManager; // co_await the reply - the coroutine is suspended until the QNetworkReply is finished. // While the coroutine is suspended, *the Qt event loop runs as usual*. const QNetworkReply *reply = co_await networkAccessManager.get(url); // Once the reply is finished, your code resumes here as if nothing amazing has just happened ;-) const auto data = reply->readAll(); ``` This library has only one class and one function that the user must be aware of: the class is [`QCoro::Task`](reference/coro/task.md) and must be used as a return type for any coroutine that `co_await`s a Qt type. The function is [`qCoro()`](reference/coro/coro.md) and it provides coroutine-friendly wrappers for Qt types that have multiple asynchronous operations that the user may want to `co_await` (for example [`QProcess`](reference/core/qprocess.md)). All the other code (basically everything in the `QCoro::detail` namespace) is here to provide the cogs and gears for the C++ coroutine machinery, making it possible to use Qt types with coroutines. The major benefit of using coroutines with Qt types is that it allows writing asynchronous code as if it were synchronous and, most importantly, while the coroutine is `co_await`ing, the __Qt event loop runs as usual__, meaning that your application remains responsive. This is a rather experimental library that I started working on to better understand coroutines in C++. After reading numerous articles and blog posts about coroutines, it still wasn't exactly clear to me how the whole thing works, so I started working on this library to get a better idea about coroutines. ## Coroutines Coroutines are regular functions, except that they can be suspended and resumed again. When a coroutine is suspended, it returns sort of a promise to the caller and the caller continues executing their code. At some point, the caller can use the newly introduced `co_await` keyword to wait for the returned promise to be fulfilled. When that happens, the caller is suspended and instead the coroutine is resumed. This allows writing asynchronous code as if it were synchronous, making it much easier to read and understand. That's not all that coroutines can do, you can read more about it in the 'Coroutines' section of this documentation. ## Supported Qt Versions QCoro supports compiling for both Qt5 and Qt6. Minimum supported versions are: * Qt5 >= 5.15.2 * Qt6 >= 6.2.0 Pass `-DUSE_QT_VERSION=5` or `-DUSE_QT_VERSION=6` to CMake to force building QCoro with respective major version of Qt. QCoro will default to Qt6 when available and fallback to Qt5 otherwise. ## Supported Compilers This library requires a compiler that supports the Coroutine TS (obviously). Currently GCC, Clang and MSVC are supported. Officially supported compilers are: * GCC >= 11 (April 2021) * Clang >= 15 (September 2022) * MSVC >= 19.40 (Visual Studio 17 2022) * AppleClang >= 15.0.0 (Xcode 15.2) In both GCC and Clang, coroutine support must be explicitly enabled. ### GCC To enable coroutines support in GCC, add `-fcoroutines` to `CXX_FLAGS`. CMake: ```cmake set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcoroutines") ``` Alternatively, just use `qcoro_enable_coroutines()` CMake macro provided by QCoro to set the flags automatically. ### Clang In Clang coroutines are still considered experimental (unlike in GCC). Coroutines are enabled by adding `-fcoroutines-ts` to `CMAKE_CXX_FLAGS`. CMake: ```cmake set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcoroutines-ts") ``` Alternatively, just use `qcoro_enable_coroutines()` CMake macro provided by QCoro to set the flags automatically. ### MSVC Coroutine support in MSVC is enabled automatically by CMake when C++20 standard is specified in `CMAKE_CXX_STANDARD`: ```cmake set(CMAKE_CXX_STANDARD 20) ``` qcoro-0.12.0/docs/macros.py000066400000000000000000000016621477357142500155270ustar00rootroot00000000000000def define_env(env): @env.macro def doctable(module, include, inherits=None, inheritedBy=[], since=None): def row(th, td): return f"{ th }{ td }" def inheritsLink(inherits): return f"""{inherits[1]}""" out = """
""" out += row("Module", module) out += row("Include", f""" ```cpp #include <{include}> ``` """) out += row("CMake", f""" ```cpp target_link_libraries(myapp QCoro::{module}) ``` """) out += row("QMake", f""" ```cpp QT += QCoro{module} ``` """) if inherits: out += row("Inherits", inheritsLink(inherits)) if inheritedBy: out += row("Inherited By", ', '.join(sorted(map(inheritsLink, inheritedBy)))) if since: out += row("Since", since) out += "
" return out qcoro-0.12.0/docs/news.md000066400000000000000000000000331477357142500151560ustar00rootroot00000000000000# Blog {{ blog_content }} qcoro-0.12.0/docs/news/000077500000000000000000000000001477357142500146405ustar00rootroot00000000000000qcoro-0.12.0/docs/news/2021/000077500000000000000000000000001477357142500152245ustar00rootroot00000000000000qcoro-0.12.0/docs/news/2021/2021-08-16-qcoro-0.1.0-announcement.md000066400000000000000000000052021477357142500230450ustar00rootroot00000000000000--- title: QCoro 0.1.0 Release Announcement date: "2021-08-16" description: I'm happy to announce first release of QCoro, a library that provides C++ coroutine support for Qt. --- # QCoro 0.1.0 Release Announcement I'm happy to announce first release of QCoro, a library that provides C++ coroutine support for Qt. You can download QCoro 0.1.0 [here][qcoro-release] or check the latest sources on [QCoro GitHub][qcoro-github]. I have talked about QCoro (and C++ coroutines in general) recently at KDE Akademy, you can view the [recording of my talk on YouTube][qcoro-youtube]. In general, QCoro provides coroutine support for various asynchronous operations provided by Qt. Since Qt doesn't support coroutines by default, QCoro provides the necessary "glue" between native Qt types and the C++ coroutine machinery, making it possible to use Qt types with coroutines easily. QCoro provides coroutine support for asynchronous operations of `QIODevice`, `QNetworkReply`, `QProcess`, `QDBusPendingReply`, `QTimer` and more. Take a look at the documentation for detailed description and list of all currently supported Qt types. A brief example from [our documentation][qcoro-docs] that demonstrates how using coroutines makes handling asynchronous operations in Qt simpler: This is a (simplified) example of how we do network requests with Qt normally, using signals and slots: ```cpp QNetworkAccessManager *manager = new QNetworkAccessManager(this); QNetworkReply *reply = manager->get(url); connect(reply, &QNetworkReply::finished, this, [this, reply]() { const auto data = reply->readAll(); doSomethingWithData(data); reply->deleteLater(); }); ``` And this is the same code, written using C++ coroutines: ```cpp QNetworkAccessManager networkAccessManager; QNetworkReply *reply = co_await networkAccessManager.get(url); const auto data = reply->readAll(); doSomethingWithData(data); reply->deleteLater(); ``` The `co_await` keyword here is the key here: it asynchronously waits for the reply to finish. During the wait, the execution returns to the caller, which could be the Qt event loop, which means that even if this code *looks* synchronous, in fact it won't block the event loop while keeping the code simple to read and understand. [qcoro-release]: https://github.com/danvratil/qcoro/releases/tag/v0.1.0 [qcoro-github]: https://github.com/danvratil/qcoro [qcoro-youtube]: https://www.youtube.com/watch?v=KKVqFqbXJaU&list=PLsHpGlwPdtMq6pJ4mqBeYNWOanjdIIPTJ&index=20 [qcoro-docs]: https://qcoro.dvratil.cz/ qcoro-0.12.0/docs/news/2021/2021-09-08-qcoro-0.2.0-announcement.md000066400000000000000000000052501477357142500230530ustar00rootroot00000000000000--- title: QCoro 0.2.0 Release Announcement date: "2021-09-08" description: > QCoro 0.2.0 has been released. The most visible change is that the library has been modularized, to follow the Qt modules and headers have been cleaned. --- # QCoro 0.2.0 Release Announcement ## Library modularity The code has been reorganized into three modules (and thus three standalone libraries): QCoroCore, QCoroDBus and QCoroNetwork. QCoroCore contains the elementary QCoro tools (`QCoro::Task`, `qCoro()` wrapper etc.) and coroutine support for some QtCore types. The QCoroDBus module contains coroutine support for types from the QtDBus module and equally the QCoroNetwork module contains coroutine support for types from the QtNetwork module. The latter two modules are also optional, the library can be built without them. It also means that an application that only uses let's say QtNetwork and has no DBus dependency will no longer get QtDBus pulled in through QCoro, as long as it only links against `libQCoroCore` and `libQCoroNetwork`. The reorganization will also allow for future support of additional Qt modules. ## Headers clean up The include headers in QCoro we a bit of a mess and in 0.2.0 they all got a unified form. All public header files now start with `qcoro` (e.g. `qcorotimer.h`, `qcoronetworkreply.h` etc.), and QCoro also provides CamelCase headers now. Thus users should simply do `#include ` if they want coroutine support for `QTimer`. The reorganization of headers makes QCoro 0.2.0 incompatible with previous versions and any users of QCoro will have to update their `#include` statements. I'm sorry about this extra hassle, but with this brings much needed sanity into the header organization and naming scheme. ## Docs update The documentation has been updated to reflect the reorganization as well as some internal changes. It should be easier to understand now and hopefully will make it easier for users to start with QCoro now. ## Internal API cleanup and code de-duplication Historically, certain types types which can be directly `co_await`ed with QCoro, for instance `QTimer` has their coroutine support implemented differently than types that have multiple asynchronous operations and thus have a coroutine-friendly wrapper classes (like `QIODevice` and it's `QCoroIODevice` wrapper). In 0.2.0 I have unified the code so that even the coroutine support for simple types like `QTimer` are implemented through wrapper classes (so there's `QCoroTimer` now) ## Full changelog [See changelog on Github](https://github.com/danvratil/qcoro/releases/tag/v0.2.0) qcoro-0.12.0/docs/news/2022/000077500000000000000000000000001477357142500152255ustar00rootroot00000000000000qcoro-0.12.0/docs/news/2022/2022-01-06-qcoro-0.4.0-announcement.md000066400000000000000000000052721477357142500230510ustar00rootroot00000000000000--- title: QCoro 0.4.0 Release Announcement date: "2022-01-06" description: > Major highlights of this release are co-installability of Qt5 and Qt6 builds of QCoro, complete re-work of CMake configuration and support for compiling QCoro with the Clang compiler against GNU's libstdc++. --- # QCoro 0.4.0 Release Announcement Major highlights in this release: * Co-installability of Qt5 and Qt6 builds of QCoro * Complete re-work of CMake configuration * Support for compiling QCoro with Clang against libstdc++ ## Co-installability of Qt5 and Qt6 builds of QCoro This change mostly affects packagers of QCoro. It is now possible to install both Qt5 and Qt6 versions of QCoro alongside each other without conflicting files. The shared libraries now contain the Qt version number in their name (e.g. `libQCoro6Core.so`) and header files are also located in dedicated subdirectories (e.g. `/usr/include/qcoro6/{qcoro,QCoro}`). User of QCoro should not need to do any changes to their codebase. ## Complete re-work of CMake configuration This change affects users of QCoro, as they will need to adjust CMakeLists.txt of their projects. First, depending on whether they want to use Qt5 or Qt6 version of QCoro, a different package must be used. Additionally, list of QCoro components to use must be specified: ``` find_package(QCoro5 REQUIRED COMPONENTS Core Network DBus) ``` Finally, the target names to use in `target_link_libraries` have changed as well: * `QCoro::Core` * `QCoro::Network` * `QCoro::DBus` The version-less `QCoro` namespace can be used regardless of whether using Qt5 or Qt6 build of QCoro. `QCoro5` and `QCoro6` namespaces are available as well, in case users need to combine both Qt5 and Qt6 versions in their codebase. This change brings QCoro CMake configuration system to the same style and behavior as Qt itself, so it should now be easier to use QCoro, especially when supporting both Qt5 and Qt6. ## Support for compiling QCoro with Clang against libstdc++ Until now, when the Clang compiler was detected, QCoro forced usage of LLVM's libc++ standard library. Coroutine support requires tight co-operation between the compiler and standard library. Because Clang still considers their coroutine support experimental it expects all coroutine-related types in standard library to be located in `std::experimental` namespace. In GNU's libstdc++, coroutines are fully supported and thus implemented in the `std` namespace. This requires a little bit of extra glue, which is now in place. ## Full changelog [See changelog on Github](https://github.com/danvratil/qcoro/releases/tag/v0.4.0) qcoro-0.12.0/docs/news/2022/2022-04-25-qcoro-0.5.0-announcement.md000066400000000000000000000077431477357142500230630ustar00rootroot00000000000000--- title: QCoro 0.5.0 Release Announcement date: "2022-04-25" description: > Major feature in the 0.5.0 release are .then() continuations for Task, all asynchronous operations in QCoro returning Task and many operations have gained support for optional timeout argument. Last but not least, coroutine-friendly wrapper for QThread has been introduced. --- # QCoro 0.5.0 Release Announcement Major features: * .then() continuation for `Task` * All asynchronous operations now return `Task` * Timeouts for many operations * Support for `QThread` ## .then() continuation for Task Sometimes it's not possible to `co_await` a coroutine - usually because you need to integrate with a 3rd party code that is not coroutine-ready. A good example might be implementing `QAbstractItemModel`, where none of the virtual methods are coroutines and thus it's not possible to use `co_await` in them. To still make it possible to all coroutines from such code, `QCoro::Task` now has a new method: `.then()`, which allows attaching a continuation callback that will be invoked by QCoro when the coroutine represented by the `Task` finishes. ```cpp void notACoroutine() { someCoroutineReturningQString().then([](const QString &result) { // Will be invoked when the someCoroutine() finishes. // The result of the coroutine is passed as an argument to the continuation. }); } ``` The continuation itself might be a coroutine, and the result of the `.then()` member function is again a `Task` (where `R` is the return type of the continuation callback), so it is possible to chain multiple continuations as well as `co_await`ing the entire chain. ## All asynchronous operations now return `Task` Up until now each operation from the QCoro wrapper types returned a special awaitable - for example, `QCoroIODevice::read()` returned `QCoro::detail::QCoroIODevice::ReadOperation`. In most cases users of QCoro do not need to concern themselves with that type, since they can still directly `co_await` the returned awaitable. However, it unnecessarily leaks implementation details of QCoro into public API and it makes it harded to return a coroutine from a non-coroutine function. As of QCoro 0.5.0, all the operations now return `Task`, which makes the API consistent. As a secondary effect, all the operations can have a chained continuation using the `.then()` continuation, as described above. ## Timeout support for many operations Qt doesn't allow specifying timeout for many operations, because they are typically non-blocking. But the timeout makes sense in most QCoro cases, because they are combination of wait + the non-blocking operation. Let's take `QIODevice::read()` for example: the Qt version doesn't have any timeout, because the call will never block - if there's nothing to read, it simply returns an empty `QByteArray`. On the other hand, `QCoroIODevice::read()` is an asynchronous operation, because under to hood, it's a coroutine that asynchronously calls a sequence of ```cpp device->waitForReadyRead(); device->read(); ``` Since `QIODevice::waitForReadyRead()` takes a timeout argument, it makes sense for `QCoroIODevice::read()` to also take (an optional) timeout argument. This and many other operations have gained support for timeout. ## Support for `QThread` It's been a while since I added a new wrapper for a Qt class, so QCoro 0.5.0 adds wrapper for `QThread`. It's now possible to `co_await` thread start and end: ```cpp std::unique_ptr thread(QThread::create([]() { ... }); ui->setLabel(tr("Starting thread..."); thread->start(); co_await qCoro(thread)->waitForStarted(); ui->setLabel(tr("Calculating...")); co_await qCoro(thread)->waitForFinished(); ui->setLabel(tr("Finished!")); ``` ## Full changelog [See changelog on Github](https://github.com/danvratil/qcoro/releases/tag/v0.5.0)
Thanks to everyone who contributed to QCoro! qcoro-0.12.0/docs/news/2022/2022-07-09-qcoro-0.6.0-announcement.md000066400000000000000000000205601477357142500230610ustar00rootroot00000000000000--- title: QCoro 0.6.0 Release Announcement date: "2022-07-09" description: > This release brings several major new featrues alongside a bunch of bufixes and improvements inside QCoro. The four major features are generators support, new QCoroWebSocket module, deprecation of task.h header, and introduction of clang-cl and apple-clang support. --- # QCoro 0.6.0 Release Announcement This release brings several major new features alongside a bunch of bugfixes and improvements inside QCoro. The four major features are: * Generator support * New QCoroWebSockets module * Deprecated task.h * Clang-cl and apple-clang support 🎉 Starting with 0.6.0 I no longer consider this library to be experimental (since clearly the experiment worked :-)) and its API to be stable enough for general use. 🎉 As always, big thank you to everyone who report issues and contributed to QCoro. Your help is much appreciated! ## Generator support Unlike regular functions (or `QCoro::Task<>`-based coroutines) which can only ever produce at most single result (through `return` or `co_return` statement), generators can yield results repeatedly without terminating. In QCoro we have two types of generators: synchronous and asynchronous. Synchronous means that the generator produces each value synchronously. In QCoro those are implemented as `QCoro::Generator`: ```cpp // A generator that produces a sequence of numbers from 0 to `end`. QCoro::Generator sequence(int end) { for (int i = 0; i <= end; ++i) { // Produces current value of `i` and suspends. co_yield i; } // End the iterator } int sumSequence(int end) { int sum = 0; // Loops over the returned Generator, resuming the generator on each iterator // so it can produce a value that we then consume. for (int value : sequence(end)) { sum += value; } return sum; } ``` The `Generator` interface implements `begin()` and `end()` methods which produce an iterator-like type. When the iterator is incremented, the generator is resumed to yield a value and then suspended again. The iterator-like interface is not mandated by the C++ standard (the C++ standard provides no requirements for generators), but it is an intentional design choice, since it makes it possible to use the generators with existing language constructs as well as standard-library and Qt features. You can find more details about synchronous generators in the [`QCoro::Generator` documentation](https://qcoro.dvratil.cz/reference/coro/generator/). Asynchronous generators work in a similar way, but they produce value asynchronously, that is the result of the generator must be `co_await`ed by the caller. ```cpp QCoro::AsyncGenerator paginator(const QUrl &baseUrl) { QUrl pageUrl = baseUrl; Q_FOREVER { pageUrl = co_await getNextPage(pageUrl); // co_awaits next page URL if (pageUrl.isNull()) { // if empty, we reached the last page break; // leave the loop } co_yield pageUrl; // finally, yield the value and suspend } // end the generator } QCoro::AsyncGenerator pageReader(const QUrl &baseUrl) { // Create a new generator auto generator = paginator(baseUrl); // Wait for the first value auto it = co_await generator.begin(); auto end = generator.end(); while (it != end) { // while the `it` iterator is valid... // Asynchronously retrieve the page content const auto content = co_await fetchPageContent(*it); // Yield it to the caller, then suspend co_yield content; // When resumed, wait for the paginator generator to produce another value co_await ++it; } } QCoro::Task<> downloader(const QUrl &baseUrl) { int page = 1; // `QCORO_FOREACH` is like `Q_FOREACH` for asynchronous iterators QCORO_FOREACH(const QString &page, pageReader(baseUrl)) { // When value is finally produced, write it to a file QFile file(QStringLiteral("page%1.html").arg(page)); file.open(QIODevice::WriteOnly); file.write(page); ++page; } } ``` Async generators also have `begin()` and `end()` methods which provide an asynchronous iterator-like types. For one, the `begin()` method itself is a coroutine and must be `co_await`ed to obtain the initial iterator. The increment operation of the iterator must then be `co_await`ed as well to obtain the iterator for the next value. Unfortunately, asynchronous iterator cannot be used with ranged-based for loops, so QCoro provides [`QCORO_FOREACH` macro](https://qcoro.dvratil.cz/reference/coro/asyncgenerator/#qcoro_foreach) to make using asynchronous generators simpler. Read the [documentation for `QCoro::AsyncGenerator`](https://qcoro.dvratil.cz/reference/coro/asyncgenerator) for more details. ## New QCoroWebSockets module The QCoroWebSockets module provides QCoro wrappers for `QWebSocket` and `QWebSocketServer` classes to make them usable with coroutines. Like the other modules, it's a standalone shared or static library that you must explicitly link against in order to be able to use it, so you don't have to worry that QCoro would pull websockets dependency into your project if you don't want to. ```cpp QCoro::Task<> ChatApp::handleNotifications(const QUrl &wsServer) { if (!co_await qCoro(mWebSocket).open(wsServer)) { qWarning() << "Failed to open websocket connection to" << wsServer << ":" << mWebSocket->errorString(); co_return; } qDebug() << "Connected to" << wsServer; // Loops whenever a message is received until the socket is disconnected QCORO_FOREACH(const QString &rawMessage, qCoro(mWebSocket).textMessages()) { const auto message = parseMessage(rawMessage); switch (message.type) { case MessageType::ChatMessage: handleChatMessage(message); break; case MessageType::PresenceChange: handlePresenceChange(message); break; case MessageType::Invalid: qWarning() << "Received an invalid message:" << message.error; break; } } } ``` The `textMessages()` methods returns an asynchronous generator, which yields the message whenever it arrives. The messages are received and enqueued as long as the generator object exists. The difference between using a generator and just `co_await`ing the next emission of the `QWebSocket::textMessage()` signal is that the generator holds a connection to the signal for its entire lifetime, so no signal emission is lost. If we were only `co_await`ing a singal emission, any message that is received before we start `co_await`ing again after handling the current message would be lost. You can find more details about the `QCoroWebSocket` and `QCoroWebSocketSever` in the [QCoro's websocket module documentation](https://qcoro.dvratil.cz/reference/websockets/). You can build QCoro without the WebSockets module by passing `-DQCORO_WITH_QTWEBSOCKETS=OFF` to CMake. ## Deprecated tasks.h header The `task.h` header and it's camelcase variant `Task` been deprecated in QCoro 0.6.0 in favor of `qcorotask.h` (and `QCoroTask` camelcase version). The main reasons are to avoid such a generic name in a library and to make the name consistent with the rest of QCoro's public headers which all start with `qcoro` (or `QCoro`) prefix. The old header is still present and fully functional, but including it will produce a warning that you should port your code to use `qcorotask.h`. You can suppress the warning by defining `QCORO_NO_WARN_DEPRECATED_TASK_H` in the compiler definitions: CMake: ```cmake add_compiler_definitions(QCORO_NO_WARN_DEPRECATED_TASK_H) ``` QMake ```qmake DEFINES += QCORO_NO_WARN_DEPRECATED_TASK_H ``` The header file will be removed at some point in the future, at latest in the 1.0 release. You can also pass `-DQCORO_DISABLE_DEPRECATED_TASK_H=ON` to CMake when compiling QCoro to prevent it from installing the deprecated task.h header. ## Clang-cl and apple-clang support The clang compiler is fully supported by QCoro since 0.4.0. This version of QCoro intruduces supports for clang-cl and apple-clang. Clang-cl is a compiler-driver that provides MSVC-compatible command line options, allowing to use clang and LLVM as a drop-in replacement for the MSVC toolchain. Apple-clang is the official build of clang provided by Apple on MacOS, which may be different from the upstream clang releases. ## Full changelog [See changelog on Github](https://github.com/danvratil/qcoro/releases/tag/v0.6.0) qcoro-0.12.0/docs/news/2022/2022-11-17-qcoro-0.7.0-announcement.md000066400000000000000000000076031477357142500230570ustar00rootroot00000000000000--- title: QCoro 0.7.0 Release Announcement date: "2022-11-16" description: > Initial QML support, new QCoro::connect() helper and many small fixes - all of this and more in QCoro 0.7.0. --- # QCoro 0.7.0 Release Announcement The major new feature in this release is initial QML support, contributed by Jonah Brüchert. Jonah also contributed `QObject::connect` helper and a coroutine version of QQuickImageProvider. As always, this release includes some smaller enhancements and bugfixes, you can find a full list of them on the Github release page. As always, big thank you to everyone who report issues and contributed to QCoro. Your help is much appreciated! ## Initial QML Support Jonah Brüchert has contributed initial support for QML. Unfortunately, we cannot extend the QML engine to support the `async` and `await` keywords from ES8, but we can make it possible to set a callback from QML that should be called when the coroutine finishes. The problem with `QCoro::Task` is that it is a template class so it cannot be registered into the QML type system and used from inside QML. The solution that Jonach has come up with is to introduce `QCoro::QmlTask` class, which can wrap any awaitable (be it `QCoro::Task` or any generic awaitable type) and provides a `then()` method that can be called from QML and that takes a JavaScript function as its only argument. The function will be invoked by `QCoro::QmlTask` when the wrapped awaitable has finished. The disadvantage of this approach is that in order to expose a class that uses `QCoro::Task` as return types of its member functions into QML, we need to create a wrapper class that converts those return types to `QCoro::QmlTask`. Luckily, we should be able to provide a smoother user experience when using QCoro in QML for Qt6 in a future QCoro release. ```cpp class QmlCoroTimer: public QObject { Q_OBJECT public: explicit QmlCoroTimer(QObject *parent = nullptr) : QObject(parent) {} Q_INVOCABLE QCoro::QmlTask start(int milliseconds) { // Implicitly wraps QCoro::Task<> into QCoro::QmlTask return waitFor(milliseconds); } private: // A simple coroutine that co_awaits a timer timeout QCoro::Task<> waitFor(int milliseconds) { QTimer timer; timer.start(milliseconds); co_await timer; } }; ... QCoro::Qml::registerTypes(); qmlRegisterType("cz.dvratil.qcoro.example", 0, 1); ``` ```qml import cz.dvratil.qcoro.example 1.0 Item { QmlCoroTimer { id: timer } Component.onCompleted: () { // Attaches a callback to be called when the QmlCoroTimer::waitFor() // coroutine finishes. timer.start(1000).then(() => { console.log("1 second elapsed!"); }); } } ``` Read the [documentation for `QCoro::QmlTask`](https://qcoro.dvratil.cz/reference/qml/qmltask) for more details. ## QCoro::connect Helper The `QCoro::connect()` helper is similar to `QObject::connect()` - except you you pass in a `QCoro::Task` instead of a sender and signal pointers. While using the `.then()` continuation can achieve similar results, the main difference is that `QCoro::connect()` takes a pointer to a context (receiver) QObject. If the receiver is destroyed before the connected `QCoro::Task` finishes, the slot is not invoked. ```cpp void MyClass::buttonClicked() { QCoro::Task task = sendNetworkRequest(); // If this object is deleted before the `task` completes, // the slot is not invoked. QCoro::connect(std::move(task), this, &handleNetworkReply); } ``` See the [QCoro documentation](https://qcoro.dvratil.cz/reference/coro/task/#interfacing-with-synchronous-functions) for more details. ## Full changelog [See changelog on Github](https://github.com/danvratil/qcoro/releases/tag/v0.7.0) qcoro-0.12.0/docs/news/2023/000077500000000000000000000000001477357142500152265ustar00rootroot00000000000000qcoro-0.12.0/docs/news/2023/2023-01-31-qcoro-0.8.0-announcement.md000066400000000000000000000057171477357142500230610ustar00rootroot00000000000000--- title: QCoro 0.8.0 Release Announcement date: "2023-01-31" description: > Improved QCoro::waitFor(), new sleepFor() and sleepUntil() helper functions and improved thread support. --- # QCoro 0.8.0 Release Announcement This is a rather small release with only two new features and one small improvement. Big thank you to [Xstrahl Inc.](https://xstrahl.com) who sponsored development of new features included in this release and of QCoro in general. And as always, thank you to everyone who reported issues and contributed to QCoro. Your help is much appreciated! ## Improved `QCoro::waitFor()` Up until this version, `QCoro::waitFor()` was only usable for `QCoro::Task`. Starting with QCoro 0.8.0, it is possible to use it with any type that satisfies the `Awaitable` concept. The concept has also been fixed to satisfies not just types with the `await_resume()`, `await_suspend()` and `await_ready()` member functions, but also types with member `operator co_await()` and non-member `operator co_await()` functions. ## `QCoro::sleepFor()` and `QCoro::sleepUntil()` Working both on QCoro codebase as well as some third-party code bases using QCoro it's clear that there's a usecase for a simple coroutine that will sleep for specified amount of time (or until a specified timepoint). It is especially useful in tests, where simulating delays, especially in asynchronous code is common. Previously I used to create small coroutines like this: ```cpp QCoro::Task<> timer(std::chrono::milliseconds timeout) { QTimer timer; timer.setSingleShot(true); timer.start(timeout); co_await timer; } ``` Now we can do the same simply by using `QCoro::sleepFor()`. Read the [documentation for `QCoro::sleepFor()`](https://qcoro.dvratil.cz/reference/core/qtimer/#qcorosleepfor) and [`QCoro::sleepUntil()`](https://qcoro.dvratil.cz/reference/core/qtimer/#qcorosleepuntil) for more details. ## `QCoro::moveToThread()` A small helper coroutine that allows a piece of function to be executed in the context of another thread. ```cpp void App::runSlowOperation(QThread *helperThread) { // Still on the main thread ui->statusLabel.setText(tr("Running")); const QString input = ui->userInput.text(); co_await QCoro::moveToThread(helperThread); // Now we are running in the context of the helper thread, the main thread is not blocked // It is safe to use `input` which was created in another thread doSomeComplexCalculation(input); // Move the execution back to the main thread co_await QCoro::moveToThread(this->thread()); // Runs on the main thread again ui->statusLabel.setText(tr("Done")); } ``` Read the [documentation for `QCoro::moveToThread`](https://qcoro.dvratil.cz/reference/core/qthread#qcoromovetothread) for more details. ## Full changelog [See changelog on Github](https://github.com/danvratil/qcoro/releases/tag/v0.8.0) qcoro-0.12.0/docs/news/2023/2023-04-27-qcoro-0.9.0-announcement.md000066400000000000000000000045131477357142500230630ustar00rootroot00000000000000--- title: QCoro 0.9.0 Release Announcement date: "2023-04-27" description: > Important bugfixes, huge improvement to QML support and a brand new Test module. --- # QCoro 0.9.0 Release Announcement Another smallish release with just a few new features, but quite important bugfixes. As always, thank you to everyone who reported issues and contributed to QCoro. Your help is much appreciated! ## Enhanced QML Support Jonah Brüchert has improved the QML support by introducing a declarative API to await a task result. Previously a task result could have only been obtained inside a JavaScript function: ```qml Label { id: usernameLabel Component.onCompleted: { api.getUserName().then(function(result) { usernameLabel.text = result }) } } ``` With QCoro 0.9.0 you can use the new declarative API to use the result of an asynchronous task in a property binding: ```qml Label { id: usernameLabel text: api.getUserName().await("Loading...").value } ``` The `Label` will now show the string "Loading..." while the asynchronous task is running and will automatically change to the result of the task once if finishes. You can check the [`QCoro::QmlTask` documentation][qcoro-qmltask-docs] for more details. ## QCoroTest Module Yet another release with a new QCoro module! This time it's for tests! The QCoroTest module contains macros (e.g., `QCORO_VERIFY`, `QCORO_COMPARE`, ...) that are basically identical to their QtTest counteparts with the only difference being that they can be used inside a coroutine. We already had this macros inside QCoro test suite almost since the very beginning and realized they can be useful to our users as well, since they will likely want to have unittests for their coroutine code. In some of the next releases we would like to add a little bit more infrastructure to make writing unittests for coroutines with QtTest even easier. Check the [`QCoroTest` module documentation][qcoro-test-docs] with a full list of the test macros. ## Full changelog [See changelog on Github](https://github.com/danvratil/qcoro/releases/tag/v0.9.0) [qcoro-qmltask-docs]: https://qcoro.dvratil.cz/reference/qml/qmltask/ [qcoro-test-docs]: https://qcoro.dvratil.cz/reference/test/ qcoro-0.12.0/docs/news/2023/2023-12-05-qcoro-0.10.0-announcement.md000066400000000000000000000100441477357142500231220ustar00rootroot00000000000000--- title: QCoro 0.10.0 Release Announcement date: "2023-12-05" description: > Improved signal/slots features and compatibility and bug fixes. --- # QCoro 0.10.0 Release Announcement Thank you to everyone who reported issues and contributed to QCoro. Your help is much appreciated! ## Support for awaiting Qt signals with QPrivateSignal Qt has a feature where signals can be made "private" (in the sense that only class that defines the signal can emit it) by appending `QPrivateSignal` argument to the signal method: ```cpp class MyObject : public QObject { Q_OBJECT ... Q_SIGNALS: void error(int code, const QString &message, QPrivateSignal); }; ``` `QPrivateSignal` is a type that is defined inside the `Q_OBJECT` macro, so it's private and as such only `MyObject` class can emit the signal, since only `MyObject` can instantiate `QPrivateSignal`: ```cpp void MyObject::handleError(int code, const QString &message) { Q_EMIT error(code, message, QPrivateSignal{}); } ``` QCoro has a feature that makes it possible to `co_await` a signal emission and returns the signals arguments as a tuple: ```cpp MyObject myObject; const auto [code, message] = co_await qCoro(&myObject, &MyObject::handleError); ``` While it was possible to `co_await` a "private" signal previously, it would get return the `QPrivateSignal` value as an additional value in the result tuple and on some occasions would not compile at all. In QCoro 0.10, we can detect the `QPrivateSignal` argument and drop it inside QCoro so that it does not cause trouble and does not clutter the result type. Achieving this wasn't simple, as it's not really possible to detect the type (because it's private), e.g. code like this would fail to compile, because we are not allowed to refer to `Obj::QPrivateSignal`, since that type is private to `Obj`. ```cpp template constexpr bool is_qprivatesignal = std::same_as_v; ``` After many different attempts we ended up abusing `__PRETTY_FUNCTION__` (and `__FUNCSIG__` on MSVC) and checking whether the function's name contains `QPrivateSignal` string in the expected location. It's a whacky hack, but hey - if it works, it's not stupid :). And thanks to improvements in compile-time evaluation in C++20, the check is evaluated completely at compile-time, so there's no runtime overhead of obtaining current source location and doing string comparisons. ## Source Code Reorganization (again!) Big part of QCoro are template classes, so there's a lot of code in headers. In my opinion, some of the files (especially qcorotask.h) were getting hard to read and navigate and it made it harder to just see the API of the class (like you get with non-template classes), which is what users of a library are usually most interested in. Therefore I decided to move definitions into separated files, so that they don't clutter the main include files. This change is completely source- and binary-compatible, so QCoro users don't have to make any changes to their code. The only difference is that the main QCoro headers are much prettier to look at now. ## Bugfixes * `QCoro::waitFor()` now re-throws exceptions ([#172][issue172], Daniel Vrátil) * Replaced deprecated `QWebSocket::error` with `QWbSocket::errorOccured` in QCoroWebSockets module ([#174][pr174], Marius P) * Fix `QCoro::connect()` not working with lambdas ([#179][pr179], Johan Brüchert) * Fix library name postfix for qmake compatibilty ([#192][pr192], Shantanu Tushar) * Fix `std::coroutine_traits isn't a class template` error with LLVM 16 ([#196][pr196], Rafael Sadowski) ## Full changelog [See changelog on Github](https://github.com/danvratil/qcoro/releases/tag/v0.10.0) [issue172]: https://github.com/danvratil/qcoro/issues/172 [pr174]: https://github.com/danvratil/qcoro/pulls/174 [pr179]: https://github.com/danvratil/qcoro/pulls/179 [pr192]: https://github.com/danvratil/qcoro/pulls/192 [pr196]: https://github.com/danvratil/qcoro/pulls/196 qcoro-0.12.0/docs/news/2024/000077500000000000000000000000001477357142500152275ustar00rootroot00000000000000qcoro-0.12.0/docs/news/2024/2024-10-04-qcoro-0.11.0-announcement.md000066400000000000000000000142141477357142500231250ustar00rootroot00000000000000--- title: QCoro 0.11.0 Release Announcement date: "2024-10-04" description: > LazyTask and many bugfixes --- # QCoro 0.11.0 Release Announcement A long over-due release which has accumulated a bunch of bugfixes but also some fancy new features...read on! As always, big thanks to everyone who reported issues and contributed to QCoro. Your help is much appreciated! ## `QCoro::LazyTask` The biggest new features in this release is the brand-new [`QCoro::LazyTask`][qcoro-lazytask]. It's a new return type that you can use for your coroutines. It differs from `QCoro::Task` in that, as the name suggest, the coroutine is evaluated lazily. What that means is when you call a coroutine that returns `LazyTask`, it will return imediately without executing the body of the coroutine. The body will be executed only once you `co_await` on the returned `LazyTask` object. This is different from the behavior of `QCoro::Task`, which is eager, meaning that it will start executing the body immediately when called (like a regular function call). ```cpp QCoro::LazyTask myWorker() { qDebug() << "Starting worker"; co_return 42; } QCoro::Task<> mainCoroutine() { qDebug() << "Creating worker"; const auto task = myWorker(); qDebug() << "Awaiting on worker"; const auto result = co_await task; // do something with the result } ``` This will result in the following output: ```plain mainCoroutine(): Creating worker mainCoroutine(): Awaiting on worker myWorker(): Starting worker ``` If `myWorker()` were a `QCoro::Task` as we know it, the output would look like this: ```plain mainCoroutine(): Creating worker myWorker(): Starting worker mainCoroutine(): Awaiting on worker ``` The fact that the body of a `QCoro::LazyTask` coroutine is only executed when `co_await`ed has one very important implication: **it must not be used for Qt slots**, `Q_INVOKABLE`s or, in general, for any coroutine that may be executed directly by the Qt event loop. The reason is, that the Qt event loop is not aware of coroutines (or QCoro), so it will never `co_await` on the returned `QCoro::LazyTask` object - which means that the code inside the coroutine would never get executed. This is the reason why the good old `QCoro::Task` is an eager coroutine - to ensure the body of the coroutine gets executed even when called from the Qt event loop and not `co_await`ed. For more details, see the [documentation of `QCoro::LazyTask`][qcoro-lazytask]. ## Defined Semantics for Awaiting Default-Constructed and Moved-From Tasks This is something that wasn't clearely defined until now (both in the docs and in the code), which is what happens when you try to `co_await` on a default-constructed `QCoro::Task` (or `QCoro::LazyTask`): ```cpp co_await QCoro::Task<>(); // will hang indefinitely! ``` Previously this would trigger a `Q_ASSERT` in debug build and most likely a crash in production build. Starting with QCoro 0.11, awaiting such task will print a `qWarning()` and will hang indefinitely. The same applies to awaiting a moved-from task, which is identical to a default-constructed task: ```cpp QCoro::LazyTask task = myTask(); handleTask(std::move(task)); co_await task; // will hang indefinitely!` ``` ## Compiler Support We have dropped official support for older compilers. Since QCoro 0.11, the officially supported compilers are: * GCC >= 11 * Clang >= 15 * MSVC >= 19.40 (Visual Studio 17 2022) * AppleClang >= 15 (Xcode 15.2) QCoro might still compile or work with older versions of those compilers, but we no longer test it and do not guarantee that it will work correctly. The reason is that coroutine implementation in older versions of GCC and clang were buggy and behaved differently than they do in newer versions, so making sure that QCoro behaves correctly across wide range of compilers was getting more difficult as we implemented more and more complex and advanced features. ## Other Features and Changes A coroutine-friendly version of [`QFuture::takeResult()`][qtdoc-qfuture-takeresult] is now available in the form of [`QCoroFuture::takeResult()`][qcorofuture-takeresult] when building QCoro against Qt 6 ([#217][issue217]). `QCoro::waitFor(QCoro::Task)` no longer requires that the task return type `T` is default-constructible ([#223][pr223], Joey Richey) ## Bugfixes * Suppress Clang error when building against Android NDK <= 25 ([#204][issue204], Daniel Vrátil) * Fixed missing QtGui dependency in QCoroQuick module ([#209][pr209], Andreas Sturmlechner) * Fixed `QCoroIODevice::write()` always returning 0 instead of bytes written ([#211][issue211], Daniel Vrátil) * Fixed unchecked `std::optional` access in `QCoroIODevice::write` * Fixed awaiting on signal emission with `qCoro()` would resume the awaiter in the sender's thread context ([#213][issue213], Daniel Vrátil) * Fixed build wilth clang 18 due to missing `#include ` ([#220][pr220], Micah Terhaar) * Fixed crash when `QNetworkAccessManager` is destroyed from a coroutine awaiting on a network reply ([#231][issue231], Daniel Vrátil) ## Full changelog [See changelog on Github](https://github.com/danvratil/qcoro/releases/tag/v0.11.0) ## Support If you enjoy using QCoro, consider supporting its development on [GitHub Sponsors][github-sponsors] or buy me a coffee on [Ko-fi][kofi] (after all, more coffee means more code, right?). [issue231]: https://github.com/danvratil/qcoro/issues/231 [issue217]: https://github.com/danvratil/qcoro/issues/217 [issue213]: https://github.com/danvratil/qcoro/issues/213 [issue211]: https://github.com/danvratil/qcoro/issues/211 [issue204]: https://github.com/danvratil/qcoro/issues/204 [pr223]: https://github.com/danvratil/qcoro/pulls/223 [pr220]: https://github.com/danvratil/qcoro/pulls/220 [pr209]: https://github.com/danvratil/qcoro/pulls/209 [qtdoc-qfuture-takeresult]: https://doc.qt.io/qt-6/qfuture.html#takeResult [qcorofuture-takeresult]: ../../reference/core/qfuture.md#takeResult [qcoro-lazytask]: ../../reference/coro/lazytask.md [github-sponsors]: https://github.com/sponsors/danvratil [kofi]: https://ko-fi.com/danvratil qcoro-0.12.0/docs/news/2025/000077500000000000000000000000001477357142500152305ustar00rootroot00000000000000qcoro-0.12.0/docs/news/2025/2025-04-03-qcoro-0.12.0-announcement.md000066400000000000000000000035521477357142500231350ustar00rootroot00000000000000--- title: QCoro 0.12.0 Release Announcement date: "2025-04-03" description: > Qt 6.9 and 6.10 support and maintenance --- # QCoro 0.12.0 Release Announcement This release brings support Qt 6.9 and dev and a bunch of bugfixes - so mostly a maintenance release. No new features, this time. As always, big thanks to everyone who reported issues and contributed to QCoro. Your help is much appreciated! ## Bugfixes * Fixed typo in QNetworkReply example ([#257][pr257], Mikhail Yatsenko) * Don't discard result from QFile::open() in ([#261][pr261], Nicolas Fella) * Fixed missing co_await in QIODevice documentation ([#264][pr264], rsp4jack) * Fixed MSVC build due to language-extension-token warning ([#266][pr266], DeveloperPaul123) * Fixed infinite recursion with debug builds on libstd++ ([#268][pr268], Jared Van Bortel) * Fixed QCoro's compile definitions leaking to user code ([#267][pr267], [#275][pr275], Daniel Vrátil) * Fixed build against Qt 6.9 ([#276][pr276], Nicolas Fella) ## Full changelog [See changelog on Github](https://github.com/danvratil/qcoro/releases/tag/v0.12.0) ## Support If you enjoy using QCoro, consider supporting its development on [GitHub Sponsors][github-sponsors] or buy me a coffee on [Ko-fi][kofi] (after all, more coffee means more code, right?). [pr257]: https://github.com/qcoro/qcoro/pull/257 [pr261]: https://github.com/qcoro/qcoro/pull/261 [pr264]: https://github.com/qcoro/qcoro/pull/264 [pr266]: https://github.com/qcoro/qcoro/pull/266 [pr267]: https://github.com/qcoro/qcoro/pull/267 [pr268]: https://github.com/qcoro/qcoro/pull/268 [pr275]: https://github.com/qcoro/qcoro/pull/275 [pr276]: https://github.com/qcoro/qcoro/pull/276 [github-sponsors]: https://github.com/sponsors/danvratil [kofi]: https://ko-fi.com/danvratil qcoro-0.12.0/docs/overrides/000077500000000000000000000000001477357142500156665ustar00rootroot00000000000000qcoro-0.12.0/docs/overrides/.gitkeep000066400000000000000000000000001477357142500173050ustar00rootroot00000000000000qcoro-0.12.0/docs/reference/000077500000000000000000000000001477357142500156225ustar00rootroot00000000000000qcoro-0.12.0/docs/reference/core/000077500000000000000000000000001477357142500165525ustar00rootroot00000000000000qcoro-0.12.0/docs/reference/core/index.md000066400000000000000000000006621477357142500202070ustar00rootroot00000000000000 # Core Module The `Core` module contains coroutine-friendly wrapper for [QtCore][qtdoc-qtcore] classes. ## CMake Usage ``` find_package(QCoro6 COMPONENTS Core) ... target_link_libraries(my-target QCoro::Core) ``` ## QMake Usage ``` QT += QCoroCore ``` [qtdoc-qtcore]: https://doc.qt.io/qt-5/qtcore-index.html qcoro-0.12.0/docs/reference/core/qfuture.md000066400000000000000000000065311477357142500205740ustar00rootroot00000000000000 # QFuture {{ doctable("Core", "QCoroFuture") }} [`QFuture`][qdoc-qfuture], which represents an asynchronously executed call, doesn't have any operation on its own that could be awaited asynchronously, this is usually done through a helper class called [`QFutureWatcher`][qdoc-qfuturewatcher]. To simplify the API, QCoro allows to directly `co_await` completion of the running `QFuture` or use of a wrapper class `QCoroFuture`. To wrap a `QFuture` into a `QCoroFuture`, use [`qCoro()`][qcoro-coro]: ```cpp template QCoroFuture qCoro(const QFuture &future); ``` It's possible to directly `co_await` a completion of a `QFuture` and obtain a result: ```cpp const QString result = co_await QtConcurrent::run(...); ``` This is a convenient alternative to using `co_await qCoro(future).result()`. ## `result()` Asynchronously waits for the `QFuture` to finish and returns the result of the future. If the future result type is `void`, then returns nothing. If the asynchronous operation has thrown an exception, it will be rethrown here. Example: ```cpp QFuture future = QtConcurrent::run(...); const QString result = co_await qCoro(future).result(); ``` This is equivalent to using `QFutureWatcher` and then retrieving the result using [`QFuture::result()`][qdoc-qfuture-result]: ```cpp QFuture future = QtConcurrent::run(...); auto watcher = new QFutureWatcher(); connect(watcher, &QFutureWatcherBase::finished, this, [watcher]() { watcher->deleteLater(); auto result = watcher.result(); ... }); watcher->addFuture(future); ``` ## `takeResult()` Asynchronously waits for the `QFuture` to finish and returns the result of the future by taking (moving) it from the future object. If the asynchronous operation has thrown an exception, it will be rethrown here. This is useful when dealing with move-only types (like `std::unique_ptr`) or when you simply want to move the result instead of copying it out from the future. Example: ```cpp QFuture> future = QtConcurrent::run(...); auto result = co_await qCoro(future).takeResult(); ``` This is equivalent to using `QFutureWatcher` and then retrieving the result using [`QFuture::takeResult()`][qdoc-qfuture-takeResult]: ```cpp QFuture> future = QtConcurrent::run(...); auto watcher = new QFutureWatcher>(); connect(watcher, &QFutureWatcherBase::finished, this, [watcher]() { watcher->deleteLater(); auto result = watcher.future().takeResult(); ... }); watcher->addFuture(future); ``` See documentation for [`QFuture::takeResult()`][qdoc-qfuture-takeResult] for limitations regarding moving the result out from `QFuture`. !!! info "This method is only available in Qt 6." ## `waitForFinished()` This is equivalent to using the [`result()`](#result) method. ## Example ```cpp {% include "../../examples/qfuture.cpp" %} ``` [qdoc-qfuture]: https://doc.qt.io/qt-5/qfuture.html [qdoc-qfuturewatcher]: https://doc.qt.io/qt-5/qfuturewatcher.html [qdoc-qfuture-result]: https://doc.qt.io/qt-6/qfuture.html#result [qdoc-qfuture-takeResult]: https://doc.qt.io/qt-6/qfuture.html#takeResult [qcoro-coro]: ../coro/coro.md qcoro-0.12.0/docs/reference/core/qiodevice.md000066400000000000000000000141221477357142500210440ustar00rootroot00000000000000 # QIODevice {{ doctable("Core", "QCoroIODevice", None, [('network/qabstractsocket', 'QCoroAbstractSocket'), ('network/qlocalsocket', 'QCoroLocalSocket'), ('network/qnetworkreply', 'QCoroNetworkReply'), ('core/qprocess', 'QCoroProcess')]) }} ```cpp class QCoroIODevice ``` [`QIODevice`][qtdoc-qiodevice] has several different IO operations that can be waited on asynchronously. Since `QIODevice` itself doesn't provide the abaility to `co_await` those operations, QCoro provides a wrapper class called `QCoroIODevice`. To wrap a `QIODevice` into a `QCoroIODevice`, use [`qCoro()`][qcoro-coro]: ```cpp QCoroIODevice qCoro(QIODevice &); QCoroIODevice qCoro(QIODevice *); ``` Note that Qt provides several subclasses of `QIODevice`. QCoro provides coroutine-friendly wrappers for some of those types as well (e.g. for [`QLocalSocket`][qlocalsocket]). This subclass can be passed to `qCoro()` function as well. Oftentimes the wrapper class will provide some additional features (like co_awaiting establishing connection etc.). You can check whether QCoro supports the QIODevice subclass by checking the list of supported Qt types. ## `readAll()` Waits until there are any data to be read from the device (similar to waiting until the device emits [`QIODevice::readyRead()`][qtdoc-qiodevice-readyread] signal) and then returns all data available in the buffer as a `QByteArray`. Doesn't suspend the coroutine if there are already data available in the `QIODevice` or if the `QIODevice` is not opened for reading. This is the default operation when `co_await`ing an instance of a `QIODevice` directly. Thus, it is possible to just do ```cpp const QByteArray content = co_await device; ``` instead of ```cpp const QByteArray content = co_await qCoro(device).readAll(); ``` See documentation for [`QIODevice::readAll()`][qtdoc-qiodevice-readall] for details. `QCoroIODevice::readAll()` additionally accepts an optional timeout parameter. If no data become available within the timeout, the coroutine returns an empty `QByteArray`. If no timeout is specified or if it is set to `-1`, the operation will never time out. ```cpp QCoro::Task QCoroIODevice::readAll(); QCoro::Task QCoroIODevice::readAll(std::chrono::milliseconds timeout); ``` ## `read()` Waits until there are any data to be read from the device (similar to waiting until the device emits [`QIODevice::readyRead()`][qtdoc-qiodevice-readyread] signal) and then returns up to `maxSize` bytes as a `QByteArray`. Doesn't suspend the coroutine if there are already data available in the `QIODevice` or if the device is not opened for reading. See documentation for [`QIODevice::read()`][qtdoc-qiodevice-read] for details. `QCoroIODevice::read()` additionally accepts an optional timeout parameter. If no data become available within the timeout, the coroutine returns an empty `QByteArray`. If no timeout is specified or if it is set to `-1`, the operation will never time out. ```cpp QCoro::Task QCoroIODevice::read(qint64 maxSize = 0); QCoro::Task QCoroIODevice::read(qint64 maxSize, std::chrono::milliseconds timeout); ``` ## `readLine()` Repeatedly waits for data to arrive until it encounters a newline character, end-of-data or until it reads `maxSize` bytes. Returns the resulting data as `QByteArray`. See documentation for [`QIODevice::readLine()`][qtdoc-qiodevice-readline] for details. `QCoroIODevice::readLine()` additionally accepts an optional timeout parameter. If no data become available within the timeout, the coroutine returns an empty `QByteArray`. If no timeout is specified or if it is set to `-1`, the operation will never time out. ```cpp QCoro::Task QCoroIODevice::readLine(qint64 maxSize = 0) QCoro::Task QCoroIODevice::readLine(qint64 maxSize, std::chrono::milliseconds timeout); ``` ## `waitForReadyRead()` Waits for at most `timeout_msecs` milliseconds for data to become available for reading in the `QIODevice`. Returns `true` when the device becomes ready for reading within the given timeout. Returns `false` if the operation times out, if the device is not opened for reading or in any other state in which the device will never become ready for reading. If the timeout is -1, the operation will never time out. See documentation for [`QIODevice::waitForReadyRead()`][qtdoc-qiodevice-waitforreadyread] for details. ```cpp QCoro::Task QCoroIODevice::waitForReadyRead(int timeout_msecs = 30'000); QCoro::Task QCoroIODevice::waitForReadyRead(std::chrono::milliseconds timeout); ``` ## `waitForBytesWritten()` Waits for at most `timeout_msecs` milliseconds for data to be flushed from a buffered `QIODevice`. Returns `std::optional`, which is empty if the operation has timed out, the device is not opened for writing or is in any other state in which the device will never be able to write any data. When the data are successfully flushed, returns number of bytes written. If the timeout is -1, the operation will never time out. See documentation for [`QIODevice::waitForBytesWritten()`][qtdoc-qiodevice-waitforbyteswritten] for details. ```cpp QCoro::Task> QCoroIODevice::waitForBytesWritten(int timeout_msecs = 30'000); QCoro::Task> QCoroIODevice::waitForBytesWritten(std::chrono::milliseconds timeout); ``` ## Examples ```cpp const QByteArray data = co_await qCoro(device).readAll(); ``` [qlocalsocket]: ../network/qlocalsocket.md [qcoro-coro]: ../coro/coro.md [qtdoc-qiodevice]: https://doc.qt.io/qt-5/qiodevice.html [qtdoc-qiodevice-read]: https://doc.qt.io/qt-5/qiodevice.html#read [qtdoc-qiodevice-readyread]: https://doc.qt.io/qt-5/qiodevice.html#readyRead [qtdoc-qiodevice-readall]: https://doc.qt.io/qt-5/qiodevice.html#readAll [qtdoc-qiodevice-readline]: https://doc.qt.io/qt-5/qiodevice.html#readLine [qtdoc-qiodevice-waitforreadyread]: https://doc.qt.io/qt-5/qiodevice.html#waitForReadyRead [qtdoc-qiodevice-waitforbyteswritten]: https://doc.qt.io/qt-5/qiodevice.html#waitForBytesWritten qcoro-0.12.0/docs/reference/core/qprocess.md000066400000000000000000000061101477357142500207310ustar00rootroot00000000000000 # QProcess {{ doctable("Core", "QCoroProcess", ("core/qiodevice", "QCoroIODevice")) }} [`QProcess`][qtdoc-qprocess] normally has two features to wait for asynchronously: the process to start and to finish. Since `QProcess` itself doesn't provide the ability to `co_await` those operations, QCoro provides a wrapper class `QCoroProcess`. To wrap a `QProcess` object into the `QCoroProcess` wrapper, use [`qCoro()`][qcoro-coro]: ```cpp QCoroProcess qCoro(QProcess &); QCoroProcess qCoro(QProcess *); ``` Same as `QProcess` is a subclass of `QIODevice`, `QCoroProcess` subclasses [`QCoroIODevice`][qcoro-qcoroiodevice], so it also provides the awaitable interface for selected QIODevice functions. See [`QCoroIODevice`][qcoro-qcoroiodevice] documentation for details. ## `waitForStarted()` Waits for the process to be started or until it times out. Returns `bool` indicating whether the process has started successfuly (`true`) or timed out (`false`). See documentation for [`QProcess::waitForStarted()`][qtdoc-qprocess-waitForStarted] for details. ```cpp QCoro::Task QCoroProcess::waitForStarted(int timeout = 30'000); QCoro::Task QCoroProcess::waitForStarted(std::chrono::milliseconds timeout); ``` ## `waitForFinished()` Waits for the process to finish or until it times out. Returns `bool` indicating whether the process has finished successfuly (`true`) or timed out (`false`). See documentation for [`QProcess::waitForFinished()`][qtdoc-qprocess-waitForFinished] for details. ```cpp QCoro::Task QCoroProcess::waitForFinishedint timeout = 30'000); QCoro::Task QCoroProcess::waitForFinished(std::chrono::milliseconds timeout); ``` ## `start()` QCoroProcess provides an additional method called `start()` which is equivalent to calling `QProcess::start()` followed by `QCoroProcess::waitForStarted()`. This operation is `co_awaitable` as well. See the documentation for [`QProcess::start()`][qtdoc-qprocess-start] and [`QProcess::waitForStarted()`][qtdoc-qprocess-waitForStarted] for details.o Returns `true` when the process has successfully started, `false` otherwise. ```cpp QCoro::Task QCoroProcess::start(QIODevice::OpenMode openMode = QIODevice::ReadOnly, std::chrono::milliseconds timeout = std::chrono::seconds(30)); QCoro::Task QCoroProcess::start(const QString &program, const QStringList &arguments, QIODevice::OpenMode openMode = QIODevice::ReadOnly, std::chrono::milliseconds timeout = std::chrono::seconds(30)); ``` ## Examples ```cpp {% include "../../examples/qprocess.cpp" %} ``` [qtdoc-qprocess]: https://doc.qt.io/qt-5/qprocess.html [qtdoc-qprocess-start]: https://doc.qt.io/qt-5/qprocess.html#start [qtdoc-qprocess-waitForStarted]: https://doc.qt.io/qt-5/qprocess.html#waitForStarted [qtdoc-qprocess-waitForFiished]: https://doc.qt.io/qt-5/qprocess.html#waitForFinished [qcoro-coro]: ../coro/coro.md [qcoro-qcoroiodevice]: qiodevice.md qcoro-0.12.0/docs/reference/core/qthread.md000066400000000000000000000045231477357142500205300ustar00rootroot00000000000000 # QThread {{ doctable("Core", "QCoroThread") }} [`QThread`][qtdoc-qthread] has two events: `started` and `finished`. QCoro provides a coroutine-friendly wrapper for `QThread` - `QCoroThread`, which allows `co_await`ing those events. To wrap a `QThread` object into the `QCoroThread` wrapper, use [`qCoro()`][qcoro-coro]: ```cpp QCoroThread qCoro(QThread &); QCoroThread qCoro(QThread *); ``` ## `waitForStarted()` Waits for the thread to start. Returns `true` if the thread is already running or when the thread starts within the specified timeout. If the thread has already finished or fails to start within the specified timeout, the coroutine will return `false`. If the timeout is set to -1, the operation will never time out. See documentation for [`QThread::started()`][qtdoc-qthread-started] for details. ```cpp QCoro::Task QCoroThread::waitForStarted(std::chrono::milliseconds timeout); ``` ## `waitForFinished()` Waits for the Waits for the process to finish or until it times out. Returns `bool` indicating whether the process has finished successfuly (`true`) or timed out (`false`). thread to finish. Returns `true` if the thread has already finished or if it finishes within the specified timeout. If the thread has not started yet or fails to stop within the specified timeout the coroutine will return `false`. If the timeout is set to -1, the operation will never time out. See documentation for [`QThread::finished()`][qtdoc-qthread-finished] for details. ```cpp QCoro::Task QCoroThread::waitForFinished(std::chrono::milliseconds timeout); ``` ## `QCoro::moveToThread()` A helper coroutine that allows changing the thread context in which the coroutine code is currently being executed. When `co_await`ed, the current coroutine will be suspended on the current thread and immediately resumed again, but in the context of the thread that was passed in as an argument. ```cpp QCoro::Task<> QCoro::moveToThread(QThread *thread); ``` ## Examples ```cpp {% include "../../examples/qthread.cpp" %} ``` [qtdoc-qthread]: https://doc.qt.io/qt-5/qthread.html [qtdoc-qthread-started]: https://doc.qt.io/qt-5/qthread.html#started [qtdoc-qthread-finished]: https://doc.qt.io/qt-5/qthread.html#finished [qcoro-coro]: ../coro/coro.md qcoro-0.12.0/docs/reference/core/qtimer.md000066400000000000000000000042271477357142500204020ustar00rootroot00000000000000 # QTimer {{ doctable("Core", "QCoroTimer") }} ```cpp QTimer timer; timer.start(1s); co_await timer; ``` The QCoro frameworks allows `co_await`ing on [QTimer][qdoc-qtimer] object. The co-awaiting coroutine is suspended, until the timer finishes, that is until [`QTimer::timeout()`][qdoc-qtimer-timeout] signal is emitted. The timer must be active. If the timer is not active (not started yet or already finished) the `co_await` expression will return immediately. To make it work, include `QCoroTimer` in your implementation. ```cpp #include #include using namespace std::chrono_literals; QCoro::Task<> MyClass::pretendWork() { // Creates and starts a QTimer that will tick every second QTimer timer; timer.setInterval(1s); timer.start(); for (int i = 1; i <= 100; ++i) { // Wait for the timer to tick co_await timer; // Update the progress bar value mProgressBar->setValue(i); // And repeat... } // ... until the for loop finishes. } ``` ## `QCoro::sleepFor()` A simple coroutine that will suspend for the specified time duration. Can be quite useful especially in unit-tests. ```cpp template QCoro::Task<> QCoro::sleepFor(const std::chrono::duration &timeout); ``` ## `QCoro::sleepUntil()` A simple coroutine that will suspend until the specified point in time. Can be useful for scheduling tasks. ```cpp template QCoro::Task< QCoro::sleepUntil(const std::chrono::time_point &when); ``` Example: ```cpp const auto now = std::chrono::system_clock::now(); const auto tomorrow_time = std::chrono::system_clock::to_time_t(now + 86400s); std::tm *gt = std::gmtime(&tomorrow_time); gt.tm_hour = 0; gt.tm_min = 0; gt.tm_sec = 0; const auto tomorrow_midnight = std::mktime(>); co_await QCoro::sleepUntil(std::chrono::system_clock::from_time_t(tomorrow_midnight)); ``` [qdoc-qtimer]: https://doc.qt.io/qt-5/qtimer.html [qdoc-qtimer-timeout]: https://doc.qt.io/qt-5/qtimer.html#timeout qcoro-0.12.0/docs/reference/core/signals.md000066400000000000000000000077731477357142500205520ustar00rootroot00000000000000 {{ doctable("Core", "QCoroSignal") }} # Signals It's possible to `co_await` a single signal emission through a special overload of the [`qCoro()`][qcoro-coro] function. The below function returns an awaitable that will suspend the current coroutine until the specified signal is emitted. ```cpp Task qCoro(QObject *obj, QtSignalPtr ptr); ``` The arguments are a pointer to a QObject-derived object and a pointer to a the object's signal to connect to. Note that if the object is destroyed while being `co_await`ed, the coroutine will never be resumed. The returned awaitable produces the signal's arguments. That is, if the signal has no arguments, the result of the awaitable will be `void`. If the signal has exactly one argument, then the awaitable produces the value of the argument. If the signal has more than one arguments, then the result of the awaitable is a `std::tuple` with all the arguments. Example: ``` // void signal co_await qCoro(timer, &QTimer::timeout); // single-argument signal const QUrl url = co_await qCoro(reply, &QNetworkReply::redirected); // multi-argument signal, captured using structured bindings const auto [exitCode, exitStatus] = co_await qCoro(process, &QProcess::finished); ``` ```cpp Task> qCoro(QObject *obj, QtSignalPtr ptr, std::chrono::milliseconds timeout); ``` An overload that behaves similar to the two-argument overload, but takes an additional `timeout` argument. If the signal is not emitted within the specified timeout, the returned awaitable produces an empty `std::optional`. Otherwise the return type behaves the same way as the two-argument overload. ## QCoroSignalListener A helper function that creates an [`AsyncGenerator`][qcoro-asyncgenerator] which yields a value whenever the signal is emitted. ```cpp QCoro::AsyncGenerator qCoroSignalListener(QObject *obj, QtSignalPtr ptr, std::chrono::milliseconds timeout); ``` The function takes up to three arguments, the `obj` and `ptr` are a QObject-derived object and a pointer to a signal member function to connect to. The third `timeout` argument is optional. When the timeout is set, the generator will end if the signal is not emitted within the specified timeout. When not set, or set to -1, the generator will never terminate on its own, even if the `obj` QObject is destroyed! The generator produces all signal emissions, even those that ocur in between the generator being `co_await`ed. In the example below, even when the `QNetworkReply::downloadProgress()` signal is emitted while asynchronously processing something in the middle of the `while` loop body, the emission will not be lost. It will be enqueued, and returned synchronously with the next `co_await ++it` call. ```cpp auto listener = qCoroSignalListener(networkReply, &QNetworkReply::downloadProgress); auto it = co_await listener.begin(); // waits for first emission while (it != listener.end() && networkReply->isRunning()) { const auto [received, total] = *it; // do something with results //... if (received == total || networkReply->isFinished()) { break; } co_await ++it; // waits for next signal emission } ``` Alternatively, it's possible to use `QCORO_FOREACH` to look over the generator: ```cpp QCORO_FOREACH(const auto [received, total], qCoroSignalListener(reply, &QNetworkReply::downloadProgress)) { // do something with `received` and `total` values if (received == total || reply->isFinished()) { break; } } ``` !!! warning "Listener doesn't stop listening until destroyed." Keep in mind that the `listener` generator will keep listening and collecting the signal emissions until it is destroyed, even if you are no longer actively iterating over the generator. It is recommended that you destroy the generator as soon as possible when you no longer need it. [qcoro-coro]: ../coro/coro.md [qcoro-asyncgenerator]: ../coro/asyncgenerator.md qcoro-0.12.0/docs/reference/coro/000077500000000000000000000000001477357142500165645ustar00rootroot00000000000000qcoro-0.12.0/docs/reference/coro/asyncgenerator.md000066400000000000000000000104121477357142500221300ustar00rootroot00000000000000 # QCoro::AsyncGenerator {{ doctable("Coro", "QCoroAsyncGenerator") }} ```cpp template class QCoro::AsyncGenerator ``` `AsyncGenerator` is fundamentally identical to [`QCoro::Generator`][qcoro-generator]. The only difference is that the value is produced asynchronously. Asynchronous generator is used exactly the same way as synchronous generators, except that the result of `AsyncGenerator::begin()` must be `co_await`ed, and incrementing the returned iterator must be `co_await`ed as well. ```cpp QCoro::AsyncGenerator lottoNumbersGenerator(int count) { Hat hat; // Hat with numbers Hand hand; // Hand to pull numbers out of the hat for (int i = 0; i < count; ++i) { const uint8_t number = co_await hand.pullNumberFrom(hat); co_yield number; // guaranteed to be a winning number } } QCoro::Task<> winningLottoNumbers() { const auto makeMeRich = lottoNumbersGenerator(10); // We must co_await begin() to obtain the initial iterator auto winningNumber = co_await makeMeRich.begin(); std::cout << "Winning numbers: "; while (winningNumber != makeMeRich.end()) { std::cout << *winningNumber; // And we must co_await increment co_await ++(winningNumber); } } ``` You might be wondering why are we `co_await`ing `AsyncGenerator::begin()` and `AsyncGeneratorIterator::operator++()`, and not just the value (the result of dereferencing the iterator) - it would surely make the code simpler and more intuitive. The simple reason is that we are `co_await`ing the next iterator (which either holds a value or is past-the-end iterator) rather than the value itself. Once we have the iterator, we must check whether it's valid or not, because paste-the-end iterator is not dereferencable. That's why the `AsyncGenerator::begin()` and `AsyncGeneratorIterator::operator++()` operations are asynchronous, rather than `AsyncGeneratorIterator::operator*()`. #### Exceptions When the generator coroutine throws an exception, the exception will be rethrown from the `operator++()` (or from generator's `begin()`) function when they are `co_await`ed. Afterwards, the iterator is considered invalid and the generator is finished and may not be used anymore. ```cpp QCoro::AsyncGenerator generatorThatThrows() { while (true) { const int val = co_await generateRandomNumber(); if (val == 0) { throw std::runtime("Division by zero!"); } co_yield 42 / val; } } QCoro::Task<> divide42ForNoReason(std::size_t count) { auto generator = generatorThatThrows(); std::vector numbers; try { // Might throw if generator generates 0 immediately. auto it = co_await generator.begin(); while (numbers.size() < count) { numbers.push_back(*it); co_await ++it; // might throw } } catch (const std::runtime_error &e) { // We were unable to generate all numbers } } ``` ### QCORO_FOREACH ```cpp #define QCORO_FOREACH(value, generator) ``` The example in previous chapter shows one example of looping over values produced by an asynchronous generator with a `while` loop. This is how it would look like with a `for` loop: ```cpp auto generator = createGenerator(); for (auto it = co_await generator.begin(), end = generator.end(); it != end; co_await ++it) { const QString &value = *it; ...//do something with value } ``` Sadly, it's not possible to use the generator with a ranged-based for loop. While initially the proposal for C++ coroutines did contain `for co_await` construct, it was removed as the committee was [concerned that it was making assumptions about the future interface of asynchronous generators][p0664r8c35]. For that reason, QCoro provides `QCORO_FOREACH` macro, which is very similar to [`Q_FOREACH`][qdoc-qforeach] and works exactly like the for-loop in the example above: ```cpp QCORO_FOREACH(const QString &value, createGenerator()) { ... // do something with value } ``` [qcoro-generator]: ./generator.md [p0664r8c35]: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0664r8.html#35 [qdoc-qforeach]: https://doc.qt.io/qt-5/qtglobal.html#Q_FOREACH qcoro-0.12.0/docs/reference/coro/coro.md000066400000000000000000000042761477357142500200610ustar00rootroot00000000000000 # qCoro() ## Wrapping Qt Types ```cpp QCoroType qCoro(QtClass *); QCoroType qCoro(QtClass &); ``` This function is overloaded for all Qt types supported by this library. It accepts either a pointer or a reference to a Qt type, and returns a QCoro type that wraps the Qt type and provides coroutine-friendly API for the type. Some objects have only a single asynchronous event, so it makes sense to make them directly `co_await`able. An example is `QTimer`, where only one thing can be `co_await`ed - the timer timeout. Thus with QCoro, it's possible to simply do this: ```cpp QTimer timer; ... co_await timer; ``` However, some Qt classes have multiple asynchronous operations that the user may want to `co_await`. For such types, simply `co_await`ing the class instance doesn't make sense since it's not clear what operation is being `co_await`ed. For those types, QCoro provides `qCoro()` function which returns a wrapper that provides coroutine-friendly versions of the asynchronous methods for the given type. Let's take QProcess as an example: one may want to `co_await` for the program to start or finish. Therefore the type must be wrapped into `qCoro()` like this: ```cpp QProcess process; // Wait for the process to be started co_await qCoro(process).start(...); // The process is running now ... ... // Wait for it to finish co_await qCoro(process).finished(); // The process is no longer running ... ``` `qCoro()` is simply overloaded for all the Qt types currently supported by the QCoro library. The function returns a wrapper object (e.g. `QCoro::detail::QCoroProcess`) which copies the QProcess API. It doesn't copy the entire API, only the bits that we want to make `co_await`able. When you call one of those metods (e.g. `QCoroProcess::start()`), it returns an awaitable type that calls `QProcess::start()`, suspends the coroutine and resumes it again when the wrapped `QProcess` object emits the `started()` signal. Normally you don't need to concern yourself with anything inside the `QCoro::detail` namespace, it's mentioned in the previous paragraph simply to explain how the wrapper works. qcoro-0.12.0/docs/reference/coro/generator.md000066400000000000000000000106301477357142500210740ustar00rootroot00000000000000 # QCoro::Generator {{ doctable("Coro", "QCoroGenerator") }} ```cpp template class QCoro::Generator ``` Generator is a coroutine that that yields a single value and then suspends itself, until resumed again. Then it yields another value and suspends again. Generator can be inifinite (the coroutine will never finish and will produce new values for ever until destroyed from the outside) or finite (after generating some amount of values the coroutine finishes). The `QCoro::Generator` is a template class providing interface for the generator consumer. There is no standard API for generators specified in the C++ standard (as of C++20). The design chosen by QCoro copies the design of cppcoro library, which is for the `Generator` class to provide `begin()` and `end()` methods to obtain iterator- like objects, allowing the generators to be used like containers and providing an interface that is familiar to every C++ programmer. ```cpp // Simple generator that produces `count` values from 0 to `(count-1)`. QCoro::Generator iota(int count) { for (int i = 0; i < count; ++i) { // Yields a value and suspends the generator. co_yield count; } } void counter() { // Creates a new initially suspended generator coroutine auto generator = iota(10); // Resumes the coroutine, obtains first value and returns // an iterator representing the first value. auto it = generator.begin(); // Obtains a past-the-end iterator. const auto end = generator.end(); // Loops until the generator doesn't finish. while (it != end) { // Resumes the generator until it co_yields another value. ++it; // Reads the current value from the iterator. std::cout << *it << std::endl; } // The code above can be written more consisely using ranged based for-loop for (const auto value : iota(10)) { std::count << value << std::endl; } } ``` ## Infinite generators A generator may be inifinite, that is it may never finish and keep producing values whenever queried. A simple naive example might be a generator producing random value whenever resumed. See the next chapter on generator lifetime regarding how to destroy an infinite generator. ```cpp QCoro::Generator randomNumberGenerator() { auto *generator = QRandomGenerator::system(); while (true) { // Generates a single random value and suspends. co_yield generator->generate(); } } void randomInitialize(QVector &vector) { // Constructs the generator auto rng = randomNumberGenerator(); // Gets the first tandom value auto rngIt = rng.begin(); // Loops over all values of the vector for (auto &val : vector) { // Stores the current random value and generates a next one val = *(rngIt++); } } // Destroyes the generator coroutine. ``` ## Generator lifetime The lifetime of the generator coroutine is tight to the lifetime of the associated `QCoro::Generator` object. The generator coroutine is destroyed when the associated `QCoro::Generator` object is destroyed, that includes the stack of the coroutine and everything allocated on the stack. It doesn't matter whether the coroutine has already finished or whether it is suspended after yielding a value. When the `QCoro::Generator` object is destroyed, the stack of the coroutine and all associated state will be destroyed using the regular rules of stack destruction. Therefore, it is stafe to allocate values on generator coroutine stack. However, dynamically allocated memory will not be free'd automatically. Therefore if you need to dynamically allocate memory on heap inside the generator coroutine, you must make sure to either free it before the generator coroutine is suspended, or that it is destroyed when the stack is destroyed, e.g. by using it in `std::unique_ptr` or `QScopeGuard`. !!! info "Memory usage" Keep in mind, that that generator coroutine will keep occupying memory even when not used until it finishes or until the associated `QCoro::Generator` is destroyed. ## Exceptions When a generator coroutine throws an exception, it will be rethrown from `operator++()` or from generator's `begin()` method. Afterwards the iterator is considered invalid and the generator is finished and may not be used anymore. qcoro-0.12.0/docs/reference/coro/index.md000066400000000000000000000024761477357142500202260ustar00rootroot00000000000000 # Coro module The Coro module contains the fundamental coroutine types - the [QCoro::Task<T>][qcoro-task] for eager coroutines, [QCoro::LazyTask<T>][qcoro-lazytask] for lazy coroutines, [QCoro::Generator<T>][qcoro-generator] for synchronous generators and [QCoro::AsyncGenerator<T>][qcoro-asyncgenerator] for asynchronous generators. Another useful bit of the Coro module is the [qCoro()][qcoro-coro] wrapper function that wraps native Qt types into a coroutine-friendly versions supported by QCoro (check the [Core][qcoro-core], [Network][qcoro-network] and [DBus][qcoro-dbus] modules of QCoro to see which Qt types are currently supported by QCoro). If you don't want to use any of the Qt types supported by QCoro in your code, but you still want to use C++ coroutines with QCoro, you can simply just link against `QCoro::Coro` target in your CMakeLists.txt. This will give you all you need to start implementing custom coroutine-native types with Qt and QCoro. [qcoro-task]: task.md [qcoro-lazytask]: lazytask.md [qcoro-coro]: coro.md [qcoro-generator]: generator.md [qcoro-asyncgenerator]: asyncgenerator.md [qcoro-core]: ../core/index.md [qcoro-network]: ../network/index.md [qcoro-dbus]: ../dbus/index.md qcoro-0.12.0/docs/reference/coro/lazytask.md000066400000000000000000000055521477357142500207570ustar00rootroot00000000000000 # QCoro::LazyTask {{ doctable("Coro", "QCoroLazyTask", None, [], "0.11") }} ```cpp template class QCoro::LazyTask ``` `QCoro::LazyTask` represents, as the name suggests, a lazily evaluated coroutine. A lazily evaluated coroutine is suspended immediately when called and will not execute its body until the returned `QCoro::LazyTask` is `co_await`ed. This is in contrast to `QCoro::Task`, which is an eager coroutine, meaning that its body is executed immediately when invoked and is only suspended when it first `co_await`s another coroutine. !!! warning "Don't use LazyTask as a Qt slot" Do not use `LazyTask` as a return type of Qt slots, or any other coroutine that can be invoked by the Qt event loop. The Qt event loop is not aware of coroutines and will never `co_await` the returned `LazyTask`. Therefore, the coroutine's body would never be executed! This is the main reason why the "default" `QCoro::Task` coroutines are eager - they don't need to be `co_await`ed in order to be executed, which makes them compatible with the Qt event loop. If you need to have a lazy coroutine that is also invokable from the Qt event loop, use an eager wrapper coroutine to pass to the event loop: ```cpp QCoro::LazyTask<> myLazyCoroutine(); Q_INVOKABLE QCoro::Task<> myLazyCoroutineWrapper() { co_await myLazyCoroutine(); } ``` The eager wrapper is always immediately executed, and since it will immediately start `co_await`ing on the lazy coroutine, the lazy coroutine will effectively get executed immediately. ## `then()` continuation It is possible to chain a continuation to the coroutine by using `.then()`. It is possible to use both lazy and eager continuations and even non-coroutine continuations: ```cpp auto task = myLazyTask().then([]() -> QCoro::Task<> { ... }); // #1 auto task = myLazyTask().then([]() -> QCoro::LazyTask<> { ... }); // #2 auto task = myLazyTask().then([]() { return 42; }) // #3 ``` In case #1, the `myLazyTask()` will be evaluated eagerly becaues of the `.then()` continuation being eager (basically equivalent to the eager wrapper mentioned in the warning note at the top). In case #2 and #3, the the entire chain will be evaluated lazily - that is, both the `myLazyTask()` and the `.then()` continuation will be evaluated only once `task` is `co_await`ed. ## Blocking wait It's possible to use `QCoro::waitFor()` to synchronously wait for completion of a lazy coroutine. Check the documentation in [`QCoro::Task`](task.md#Blocking-wait) for details. ## Interfacing with synchronous functions See [`QCoro::connect() documentation for `QCoro::Task`](task.md#interfacing-with-synchronous-functions) for details. qcoro-0.12.0/docs/reference/coro/task.md000066400000000000000000000207751477357142500200630ustar00rootroot00000000000000 # QCoro::Task {{ doctable("Coro", "QCoroTask") }} ```cpp template class QCoro::Task ``` Any coroutine that wants to `co_await` one of the types supported by the QCoro library must have return type `QCoro::Task`, where `T` is the type of the "regular" coroutine return value. There's no need by the user to interact with or construct `QCoro::Task` manually, the object is constructed automatically by the compiler before the user code is executed. To return a value from a coroutine, use `co_return`, which will store the result in the `Task` object and leave the coroutine. ```cpp QCoro::Task getUserName(UserID userId) { ... // Obtain a QString by co_awaiting another coroutine const QString result = co_await fetchUserNameFromDb(userId); ... // Return the QString from the coroutine as you would from a regular function, // just use `co_return` instead of `return` keyword. co_return result; } ``` To obtain the result of a coroutine that returns `QCoro::Task`, the result must be `co_await`ed. When the coroutine `co_return`s a result, the result is stored in the `Task` object and the `co_await`ing coroutine is resumed. The result is obtained from the returned `Task` object and returned as a result of the `co_await` call. ```cpp QCoro::Task getUserDetails(UserID userId) { ... const QString name = co_await getUserName(userId); ... } ``` !!! info "Exception Propagation" When coroutines throws an unhandled exception, the exception is stored in the `Task` object and is re-thrown from the `co_await` call in the awaiting coroutine. Note that a default-constructed `Task` object is not associated with any coroutine and awaiting on it will suspend the awaiter indefinitely. Moving into a default constructed `Task` associates it with the coroutine previously associated with the moved-from `Task`, and so awaiting on the moved-to `Task` will work as expcted. On the other hand, the moved-from `Task` will no longer be associated with any coroutine and awaiting on it will behave the same as awaiting a default- constructed task - it will suspend the awaiter indefinitely. ## `then()` continuation !!! note "This feature is available since QCoro 0.5.0" Sometimes it's not possible to `co_await` a coroutine, for example when calling a coroutine from a reimplementation of a virtual function from a 3rd party library, where we cannot change the signature of that function to be a coroutine (e.g. a reimplementation of `QAbstractItemModel::data()`). Even in this case, we want to process the result of the coroutine asynchronously, though. For such cases, `Task` provides a `then()` member function that allows the caller to provide a custom continuation callback to be invoked when the coroutine finishes. --- ```cpp template requires (std::invocable || (!std::is_void && std::invocable)) Task Task::then(ThenCallback callback); ``` The `Task::then()` member function has two arguments. The first argument is the continuation that is called when the coroutine finishes. The second argument is optional - it is a callable that gets invoked instead of the continuation when the coroutine throws an exception. The continuation callback must be a callable that accepts either zero arguments (effectively discardin the result of the coroutine), or exactly one argument of type `T` or type implicitly constructible from `T`. If the return type of the `ThenCallback` is `void`, then the return type of the `then()` functon is `Task`. If the return type of the `ThenCallback` is `R` or `Task`, the return type of the `then()` function is `Task`. This means that the `ThenCallback` can be a coroutine as well. Thanks to the return type always being of type `Task`, it is possible to chain multiple `.then()` calls, or `co_await` the result of the entire chain. If the coroutine throws an exception, the exception is re-thrown when the result of the entire continuation is `co_await`ed. If the result of the continuation is not `co_await`ed, the exception is silently ignored. If an exception is thrown from the `ThenCallback`, then the exception is either propagated to the nex chained `then()` continuation or re-thrown if directly `co_await`ed. If the result is not `co_await`ed and no futher `then()` continuation is chained after the one that has thrown, then the exception is silently ignored. --- ```cpp template requires (((std::is_void_t && std::invocable) || std::invocable) && std::invocable) Task Task::then(ThenCallback thenCallback, ErrorCallback errorCallback); ``` An overload of the `then()` member function which takes an additional callback to be invoked when an exception is thrown from the coroutine. The `ErrorCallback` must be a callable that takes exactly one argument, which is `const std::exception &`, holding reference to the exception thrown. An exception thrown from the `ErrorCallback` will be re-thrown if the entire continuation is `co_await`ed. If another `.then()` continuation is chained after the current continuation and has an `ErrorCallback`, then the `ErrorCallback` will be invoked. Otherwise, the exception is silently ignored. If an exception is thrown by the non-void coroutine and is handled by the `ErrorCallback`, then if the resulting continuation is `co_await`ed, the result will be a default-constructed instance of type `R` (since the `ThenCallback` was unable to provide a proper instance of type `R`). If `R` is not default- constructible, the program will not compile. Thus, if returning a non-default-constructible type from a coroutine that may throw an exception, we recommend to wrap the type in `std::optional`. Examples: ```cpp QString User::name() { if (mName.isNull()) { mApi.fetchUserName().then( [this](const QString &name) { mName = name; Q_EMIT nameChanged(); }, [](const std::exception &e) { mName = QStringLiteral("Failed to fetch name: %1").arg(e.what()); Q_EMIT nameChanged(); }); return QStringLiteral("Loading..."); } else { return mName; } } ``` ## Blocking wait Sometimes it's necessary to wait for a coroutine in a blocking manner - this is especially useful in tests where possibly no event loop is running. QCoro has `QCoro::waitFor()` function which takes `QCoro::Task` (that is, result of calling any QCoro-based coroutine) and blocks until the coroutine finishes. If the coroutine has a non-void return value, the value is returned from `waitFor().` Since QCoro 0.8.0 it is possible to use `QCoro::waitFor()` with any awaitable type, not just `QCoro::Task`. ```cpp QCoro::Task computeAnswer() { co_await QCoro::sleepFor(std::chrono::years{7'500'00}); co_return 42; } void nonCoroutineFunction() { // The following line will block as if computeAnswer were not a coroutine. It will internally run a // a QEventLoop, so other events are still processed. const int answer = QCoro::waitFor(computeAnswer()); std::cout << "The answer is: " << answer << std::endl; } ``` !!! info "Event loops" The implementation internally uses a `QEventLoop` to wait for the coroutine to be completed. This means that a `QCoreApplication` instance must exist, although it does not need to be executed. Usual warnings about using a nested event loop apply here as well. ## Interfacing with synchronous functions !!! note "This feature is available since QCoro 0.7.0" Sometimes you need to interface with code that is not coroutine-aware, for example when building list models. If you'd use the normal `.then()` function, and the coroutine would finish after the model is deleted, the program would crash. The `QCoro::connect` function is similar to `QObject::connect`, but for `QCoro::Task`s. Just like `QObject::connect`, it only calls its callback if the context object still exists. This is an example for a function that fetches data and updates a model: ```cpp void updateModel() { auto task = someCoroutine(); QCoro::connect(std::move(task), this, [this](auto &&result) { beginResetModel(); m_entries = std::move(result); endResetModel(); }); } ``` If the model is deleted before the coroutine finishes, the connected lambda will not be called. qcoro-0.12.0/docs/reference/dbus/000077500000000000000000000000001477357142500165575ustar00rootroot00000000000000qcoro-0.12.0/docs/reference/dbus/index.md000066400000000000000000000006621477357142500202140ustar00rootroot00000000000000 # DBus Module The `DBus` module contains coroutine-friendly wrapper for [QtDBus][qtdoc-qtdbus] classes. ## CMake Usage ``` find_package(QCoro6 COMPONENTS DBus) ... target_link_libraries(my-target QCoro::DBus) ``` ## QMake Usage ``` QT += QCoroDBus ``` [qtdoc-qtdbus]: https://doc.qt.io/qt-5/qtdbus-index.html qcoro-0.12.0/docs/reference/dbus/qdbuspendingcall.md000066400000000000000000000052151477357142500224230ustar00rootroot00000000000000 # QDBusPendingCall {{ doctable("DBus", "QCoroDBusPendingCall") }} [`QDBusPendingCall`][qdoc-qdbuspendingcall] on its own doesn't have any operation that could be awaited asynchronously, this is usually done through a helper class called [`QDBusPendingCallWatcher`][qdoc-qdbuspendingcallwatcher]. To simplify the API, QCoro allows to directly `co_await` completion of the pending call or use a wrapper class `QCoroDBusPendingCall`. To wrap a `QDBusPendingCall` into a `QCoroDBusPendingCall`, use [`qCoro()`][qcoro-coro]: ```cpp QCoroDBusPendingCall qCoro(const QDBusPendingCall &); ``` To await completion of the pending call without the `qCoro` wrapper, just use the pending call in a `co_await` expression. The behavior is identical to awaiting on result of `QCoroDBusPendingCall::waitForFinished()`. ```cpp QDBusPendingCall call = interface.asyncCall(...); const QDBusReply<...> reply = co_await pendigCall; ``` !!! info "`QDBusPendingCall` vs. `QDBusPendingReply`" As the Qt documentation for [`QDBusPendingCall`][qdoc-qdbuspendingcall] says, you are more likely to use [`QDBusPendingReply`][qdoc-qdbuspendingreply] in application code than `QDBusPendingCall`. QCoro has explicit support for `QDBusPendingCall` to allow using functions that return `QDBusPendingCall` directly in `co_await` expressions without the programmer having to first convert it to `QDBusPendingReply`. `QDBusPendingReply` can be constructed from a `QDBusMessage`, which is a result of awaiting `QDBusPendingCall`, therefore it's possible to perform both the conversion and awaiting in a single line of code: ```cpp QDBusPendingReply<...> reply = co_await iface.asyncCall(...); ``` Note that [`QDBusAbstractInterface::asyncCall`][qdoc-qdbusabstractinterface-asyncCall] returns a `QDBusPendingCall`. ## `waitForFinished()` {% include-markdown "../../../qcoro/dbus/qcorodbuspendingcall.h" dedent=true rewrite-relative-urls=false start="" end="" %} ## Example ```cpp {% include "../../examples/qdbus.cpp" %} ``` [qdoc-qdbuspendingcall]: https://doc.qt.io/qt-5/qdbuspendingcall.html [qdoc-qdbuspendingreply]: https://doc.qt.io/qt-5/qdbuspendingreply.html [qdoc-qdbuspendingcallwatcher]: https://doc.qt.io/qt-5/qdbuspendingcallwatcher.html [qdoc-qdbuspendingcallwatcher-finished]: https://doc.qt.io/qt-5/qdbuspendingcallwatcher.html#finished [qdoc-qdbusabstractinterface-asyncCall]: https://doc.qt.io/qt-5/qdbusabstractinterface.html#asyncCall-1 [qcoro-coro]: ../coro/coro.md qcoro-0.12.0/docs/reference/dbus/qdbuspendingreply.md000066400000000000000000000045621477357142500226470ustar00rootroot00000000000000 # QDBusPendingReply {{ doctable("DBus", "QCoroDBusPendingReply") }} [`QDBusPendingReply`][qdoc-qdbuspendingreply] on its own doesn't have any operation that could be awaited asynchronously, this is usually done through a helper class called [`QDBusPendingCallWatcher`][qdoc-qdbuspendingcallwatcher]. To simplify the API, QCoro allows to directly `co_await` completion of the pending reply or use a wrapper class `QCoroDBusPendingReply`. To wrap a `QDBusPendingReply` into a `QCoroDBusPendingReply`, use [`qCoro()`][qcoro-coro]: ```cpp template QCoroDBusPendingCall qCoro(const QDBusPendingReply &); ``` !!! info "`QDBusPendingReply` in Qt5 vs Qt6" `QDBusPendingReply` in Qt6 is a variadic template, meaning that it can take any amount of template arguments. In Qt5, however, `QDBusPendingReply` is a template class that accepts only up to 8 paremeters. In QCoro the `QCoroDBusPendingReply` wrapper is implemented as a variadic template for compatibility with Qt6, but when building against Qt5, the number of template parameters is artificially limited to 8 to mirror the limitation of Qt5 `QDBusPendingReply` limitation. To await completion of the pending call without the `qCoro` wrapper, just use the pending call in a `co_await` expression. The behavior is identical to awaiting on result of `QCoroDBusPendingReply::waitForFinished()`. ```cpp QDBusPendingReply<...> reply = interface.asyncCall(...); co_await reply; // Now the reply is finished and the result can be retrieved. ``` ## `waitForFinished()` {% include-markdown "../../../qcoro/dbus/qcorodbuspendingreply.h" dedent=true rewrite-relative-urls=false start="" end="" %} ## Example ```cpp {% include "../../examples/qdbus.cpp" %} ``` [qdoc-qdbuspendingcall]: https://doc.qt.io/qt-5/qdbuspendingcall.html [qdoc-qdbuspendingreply]: https://doc.qt.io/qt-5/qdbuspendingreply.html [qdoc-qdbuspendingcallwatcher]: https://doc.qt.io/qt-5/qdbuspendingcallwatcher.html [qdoc-qdbuspendingcallwatcher-finished]: https://doc.qt.io/qt-5/qdbuspendingcallwatcher.html#finished [qdoc-qdbusabstractinterface-asyncCall]: https://doc.qt.io/qt-5/qdbusabstractinterface.html#asyncCall-1 [qcoro-coro]: ../coro/coro.md qcoro-0.12.0/docs/reference/network/000077500000000000000000000000001477357142500173135ustar00rootroot00000000000000qcoro-0.12.0/docs/reference/network/index.md000066400000000000000000000007141477357142500207460ustar00rootroot00000000000000 # Network Module The `Network` module contains coroutine-friendly wrapper for [QtNetwork][qtdoc-qtnetwork] classes. ## CMake Usage ``` find_package(QCoro6 COMPONENTS Network) ... target_link_libraries(my-target QCoro::Network) ``` ## QMake Usage ``` QT += QCoroNetwork ``` [qtdoc-qtnetwork]: https://doc.qt.io/qt-5/qtnetwork-index.html qcoro-0.12.0/docs/reference/network/qabstractsocket.md000066400000000000000000000106421477357142500230350ustar00rootroot00000000000000 # QAbstractSocket {{ doctable("Network", "QCoroAbstractSocket", ("core/qiodevice", "QCoroIODevice")) }} [`QAbstractSocket`][qtdoc-qabstractsocket] is a base class for [`QTcpSocket`][qtdoc-qtcpsocket] and [`QUdpSocket`][qtdoc-qudpsocket] and has some potentially asynchronous operations. In addition to reading and writing, which are provided by [`QIODevice`][qtdoc-qiodevice] baseclass and can be used with coroutines thanks to QCoro's [`QCoroIODevice`][qcoro-qcoroiodevice] it provides asynchronous waiting for connecting to and disconnecting from the server. Since `QAbstractSocket` doesn't provide the ability to `co_await` those operations, QCoro provides a wrapper calss `QCoroAbstractSocket`. To wrap a `QAbstractSocket` object into the `QCoroAbstractSocket` wrapper, use [`qCoro()`][qcoro-coro]: ```cpp QCoroAbstractSocket qCoro(QAbstractSocket &); QCoroAbstractSocket qCoro(QAbstractSocket *); ``` Same as `QAbstractSocket` is a subclass of `QIODevice`, `QCoroAbstractSocket` subclasses [`QCoroIODevice`][qcoro-qcoroiodevice], so it also provides the awaitable interface for selected `QIODevice` functions. See [`QCoroIODevice`][qcoro-qcoroiodevice] documentation for details. ## `waitForConnected()` Waits for the socket to connect or until it times out. Returns `bool` indicating whether connection has been established (`true`) or whether the operation has timed out or another error has occurred (`false`). Returns immediatelly when the socket is already in connected state. If the timeout is -1, the operation will never time out. See documentation for [`QAbstractSocket::waitForConnected()`][qtdoc-qabstractsocket-waitForConnected] for details. ```cpp QCoro::Task QCoroAbstractSocket::waitForConnected(int timeout_msecs = 30'000); QCoro::Task QCoroAbstractSocket::waitForConnected(std::chrono::milliseconds timeout); ``` ## `waitForDisconnected()` Waits for the socket to disconnect from the server or until the operation times out. Returns immediatelly if the socket is not in connected state. If the timeout is -1, the operation will never time out. See documentation for [`QAbstractSocket::waitForDisconnected()`][qtdoc-qabstractsocket-waitForDisconnected] for details. ```cpp QCoro::Task QCoroAbstractSocket::waitForDisconnected(timeout_msecs = 30'000); QCoro::Task QCoroAbstractSocket::waitForDisconnected(std::chrono::milliseconds timeout); ``` ## `connectToHost()` `QCoroAbstractSocket` provides an additional method called `connectToHost()` which is equivalent to calling `QAbstractSocket::connectToHost()` followed by `QAbstractSocket::waitForConnected()`. This operation is co_awaitable as well. If the timeout is -1, the operation will never time out. See the documentation for [`QAbstractSocket::connectToHost()`][qtdoc-qabstractsocket-connectToHost] and [`QAbstractSocket::waitForConnected()`][qtdoc-qabstractsocket-waitForConnected] for details. ```cpp QCoro::Task QCoroAbstractSocket::connectToHost(const QHostAddress &address, quint16 port, QIODevice::OpenMode openMode = QIODevice::ReadOnly, std::chrono::milliseconds timeout = std::chrono::seconds(30)); QCoro::Task QCoroAbstractSocket::connectToHost(const QString &hostName, quint16 port, QIODevice::OpenMode openMode = QIODevice::ReadOnly, QAbstractSocket::NetworkLayerProtocol protocol = QAbstractSocket::AnyIPProtocol, std::chrono::milliseconds timeout = std::chrono::seconds(30)); ``` ## Examples ```cpp {% include "../../examples/qtcpsocket.cpp" %} ``` [qtdoc-qiodevice]: https://doc.qt.io/qt-5/qiodevice.html [qtdoc-qtcpsocket]: https://doc.qt.io/qt-5/qtcpsocket.html [qtdoc-qudpsocket]: https://doc.qt.io/qt-5/qudpsocket.html [qtdoc-qabstractsocket]: https://doc.qt.io/qt-5/qabstractsocket.html [qtdoc-qabstractsocket-connectToServer]: https://doc.qt.io/qt-5/qabstractsocket.html#connectToServer [qtdoc-qabstractsocket-waitForConnected]: https://doc.qt.io/qt-5/qabstractsocket.html#waitForConnected [qtdoc-qabstractsocket-waitForDisconnected]: https://doc.qt.io/qt-5/qabstractsocket.html#waitForDisconnected [qcoro-coro]: ../coro/coro.md [qcoro-qcoroiodevice]: ../core/qiodevice.md qcoro-0.12.0/docs/reference/network/qlocalsocket.md000066400000000000000000000102571477357142500223260ustar00rootroot00000000000000 # QLocalSocket {{ doctable("Network", "QCoroLocalSocket", ("core/qiodevice", "QCoroIODevice")) }} [`QLocalSocket`][qtdoc-qlocalsocket] has several potentially asynchronous operations in addition to reading and writing, which are provided by [`QIODevice`][qtdoc-qiodevice] baseclass and can be used with coroutines thanks to QCoro's [`QCoroIODevice`][qcoro-qcoroiodevice]. Those operations are connecting to and disconnecting from the server. Since `QLocalSocket` doesn't provide the ability to `co_await` those operations, QCoro provides a wrapper calss `QCoroLocalSocket`. To wrap a `QLocalSocket` object into the `QCoroLocalSocket` wrapper, use [`qCoro()`][qcoro-coro]: ```cpp QCoroLocalSocket qCoro(QLocalSocket &); QCoroLocalSocket qCoro(QLocalSocket *); ``` Same as `QLocalSocket` is a subclass of `QIODevice`, `QCoroLocalSocket` subclasses [`QCoroIODevice`][qcoro-qcoroiodevice], so it also provides the awaitable interface for selected `QIODevice` functions. See [`QCoroIODevice`][qcoro-qcoroiodevice] documentation for details. ## `waitForConnected()` Waits for the socket to connect or until it times out. Returns `bool` indicating whether connection has been established (`true`) or whether the operation has timed out or another error has occurred (`false`). Returns immeditelly if the socket is already in connected state. If the timeout is -1, the operation will never time out. See documentation for [`QLocalSocket::waitForConnected()`][qtdoc-qlocalsocket-waitForConnected] for details. ```cpp QCoro::Task QCoroLocalSocket::waitForConnected(int timeout_msecs = 30'000); QCoro::Task QCoroLocalSocket::waitForConnected(std::chrono::milliseconds timeout); ``` ## `waitForDisconnected()` Waits for the socket to disconnect from the server or until the operation times out. Returns immediatelly if the socket is not in connected state. If the timeout is -1, the operation will never time out. See documentation for [`QLocalSocket::waitForDisconnected()`][qtdoc-qlocalsocket-waitForDisconnected] for details. ```cpp QCoro::Task QCoroLocalSocket::waitForDisconnected(timeout_msecs = 30'000); QCoro::Task QCoroLocalSocket::waitForDisconnected(std::chrono::milliseconds timeout); ``` ## `connectToServer()` `QCoroLocalSocket` provides an additional method called `connectToServer()` which is equivalent to calling `QLocalSocket::connectToServer()` followed by `QLocalSocket::waitForConnected()`. This operation is co_awaitable as well. If the timeout is -1, the operation will never time out. See the documentation for [`QLocalSocket::connectToServer()`][qtdoc-qlocalsocket-connectToServer] and [`QLocalSocket::waitForConnected()`][qtdoc-qlocalsocket-waitForConnected] for details. ```cpp QCoro::Task QCoroLocalSocket::connectToServer(QIODevice::OpenMode openMode = QIODevice::ReadOnly, std::chrono::milliseconds timeout = std::chrono::seconds(30)); QCoro::Taks QCoroLocalSocket::connectToServer(const QString &name, QIODevice::OpenMode openMode = QIODevice::ReadOnly, std::chrono::milliseconds timeout = std::chrono::seconds(30)); ``` ## Examples ```cpp QCoro::Task requestDataFromServer(const QString &serverName) { QLocalSocket socket; if (!co_await qCoro(socket).connectToServer(serverName)) { qWarning() << "Failed to connect to the server"; co_return QByteArray{}; } socket.write("SEND ME DATA!"); QByteArray data; while (!data.endsWith("\r\n.\r\n")) { data += co_await qCoro(socket).readAll(); } co_return data; } ``` [qtdoc-qiodevice]: https://doc.qt.io/qt-5/qiodevice.html [qtdoc-qlocalsocket]: https://doc.qt.io/qt-5/qlocalsocket.html [qtdoc-qlocalsocket-connectToServer]: https://doc.qt.io/qt-5/qlocalsocket.html#connectToServer [qtdoc-qlocalsocket-waitForConnected]: https://doc.qt.io/qt-5/qlocalsocket.html#waitForConnected [qtdoc-qlocalsocket-waitForDisconnected]: https://doc.qt.io/qt-5/qlocalsocket.html#waitForDisconnected [qcoro-coro]: ../coro/coro.md [qcoro-qcoroiodevice]: ../core/qiodevice.md qcoro-0.12.0/docs/reference/network/qnetworkreply.md000066400000000000000000000025271477357142500225710ustar00rootroot00000000000000 # QNetworkReply {{ doctable("Network", "QCoroNetworkReply", ("network/qiodevice", "QCoroIODevice")) }} [`QNetworkReply`][qdoc-qnetworkreply] has two asynchronous aspects: one is waiting for the reply to finish, and one for reading the response data as they arrive. QCoro supports both. `QNetworkReply` is a subclass of [`QIODevice`][qdoc-qiodevice], so you can leverage all the features of [`QCoroIODevice`][qcoro-iodevice] to asynchronously read data from the underlying `QIODevice` using coroutines. To wait for the reply to finish, one can simply `co_await` the reply object: ```cpp QNetworkAccessManager nam; auto *reply = co_await nam.get(request); ``` The QCoro frameworks allows `co_await`ing on [QNetworkReply][qdoc-qnetworkreply] objects. The co-awaiting coroutine is suspended, until [`QNetworkReply::finished()`][qdoc-qnetworkreply-finished] signal is emitted. To make it work, include `QCoroNetworkReply` in your implementation. ```cpp {% include "../../examples/qnetworkreply.cpp" %} ``` [qdoc-qnetworkreply]: https://doc.qt.io/qt-5/qnetworkreply.html [qdoc-qnetworkreply-finished]: https://doc.qt.io/qt-5/qnetworkreply.html#finished [qdoc-qiodevice]: https://doc.qt.io/qt-5/qiodevice.html [qcoro-iodevice]: ../core/qiodevice.md qcoro-0.12.0/docs/reference/network/qtcpserver.md000066400000000000000000000026641477357142500220430ustar00rootroot00000000000000 # QTcpServer {{ doctable("Network", "QCoroTcpServer") }} [`QTcpServer`][qtdoc-qtcpserver] really only has one asynchronous operation worth `co_await`ing, and that's `waitForNewConnection()`. Since `QTcpServer` doesn't provide the ability to `co_await` those operations, QCoro provides a wrapper class `QCoroTcpServer`. To wrap a `QTcpServer` object into the `QCoroTcpServer` wrapper, use [`qCoro()`][qcoro-coro]: ```cpp QCoroTcpServer qCoro(QTcpServer &); QCoroTcpServer qCoro(QTcpServer *); ``` ## `waitForNewConnection()` Waits until a new incoming connection is available or until it times out. Returns pointer to `QTcpSocket` or `nullptr` if the operation timed out or another error has occured. If the timeout is -1 the operation will never time out. See documentation for [`QTcpServer::waitForNewConnection()`][qtdoc-qtcpserver-waitForNewConnection] for details. ```cpp QCoro::Task QCoroTcpServer::waitForNewConnection(int timeout_msecs = 30'000); QCoro::Task QCoroTcpServer::waitForNewConnection(std::chrono::milliseconds timeout); ``` ## Examples ```cpp {% include "../../examples/qtcpserver.cpp" %} ``` [qtdoc-qtcpserver]: https://doc.qt.io/qt-5/qtcpserver.html [qtdoc-qtcpserver-waitForNewConnection]: https://doc.qt.io/qt-5/qtcpserver.html#waitForNewConnection [qcoro-coro]: ../coro/coro.md qcoro-0.12.0/docs/reference/qml/000077500000000000000000000000001477357142500164135ustar00rootroot00000000000000qcoro-0.12.0/docs/reference/qml/index.md000066400000000000000000000011731477357142500200460ustar00rootroot00000000000000 # QML Module The `QML` module contains coroutine-friendly wrappers for [QtQml][qtdoc-qml] classes. ## CMake Usage ```cmake find_package(QCoro6 COMPONENTS Qml) ... target_link_libraries(my-target QCoro::Qml) ``` ## QMake Usage ``` QT += QCoroQml ``` ## Type registration To use types defined in QCoroQml, you need to call the `QCoro::Qml::registerTypes` function before loading the QML. ```C++ int main() { ... QCoro::Qml::registerTypes(); ... } ``` [qtdoc-qml]: https://doc.qt.io/qt-5/qml-index.html qcoro-0.12.0/docs/reference/qml/qmltask.md000066400000000000000000000023671477357142500204210ustar00rootroot00000000000000 # QmlTask {{ doctable("Qml", "QCoroQmlTask") }} QmlTask allows to return [QCoro::Task][qcoro-task]s directly to QML. It can be constructed from any [QCoro::Task][qcoro-task] that returns a value that can be converted to a [QVariant][qdoc-qml]. ```C++ #include #include int main() { ... qmlRegisterType("io.me.qmlmodule", 1, 0, "Example"); QCoro::Qml::registerTypes(); ... } class Example : public QObject { Q_OBJECT ... public: Q_INVOKABLE QCoro::QmlTask fetchValue(const QString &name) const { return database->fetchValue(name); // Returns QCoro::Task } } ``` QmlTasks can call a JavaScript function when they complete: ```QML Example { Component.onCompleted: { fetchValue("key").then((result) => console.log("Result", result)) } } ``` They can also set properties when the value is available: ```QML import QCoro 0 import io.me.qmlmodule 1.0 Item { Example { id: store } Label { text: store.fetchValue("key").await().value } } ``` [qdoc-qml]: https://doc.qt.io/qt-5/qvariant.html [qcoro-task]: ../coro/task.md qcoro-0.12.0/docs/reference/quick/000077500000000000000000000000001477357142500167365ustar00rootroot00000000000000qcoro-0.12.0/docs/reference/quick/imageprovider.md000066400000000000000000000015551477357142500221230ustar00rootroot00000000000000 # ImageProvider {{ doctable("Quick", "QCoroImageProvider") }} Coroutines based implementation of [`QQuickImaqeProvider`][qdoc-imageprovider]. To use `QCoro::ImageProvider`, you need to create a subclass of it, and implement the `asyncRequestImage` function, as shown in the example below: ```cpp #include class IconImageProvider : public QCoro::ImageProvider { public: QCoro::Task asyncRequestImage(const QString &id, const QSize &) override; }; ``` The subclass [can be registered with a `QQmlEngine`][qdoc-addimageprovider] like any `QQuickImageProvider` subclass. [qdoc-addimageprovider]: https://doc.qt.io/qt-5/qqmlengine.html#addImageProvider [qdoc-imageprovider]: https://doc.qt.io/qt-5/qquickimageprovider.html qcoro-0.12.0/docs/reference/quick/index.md000066400000000000000000000006741477357142500203760ustar00rootroot00000000000000 # Quick Module The `Quick` module contains coroutine-friendly wrappers for [QtQuick][qtdoc-qtquick] classes. ## CMake Usage ```cmake find_package(QCoro6 COMPONENTS Quick) ... target_link_libraries(my-target QCoro::Quick) ``` ## QMake Usage ``` QT += QCoroQuick ``` [qtdoc-qtquick]: https://doc.qt.io/qt-5/qtquick-index.html qcoro-0.12.0/docs/reference/test/000077500000000000000000000000001477357142500166015ustar00rootroot00000000000000qcoro-0.12.0/docs/reference/test/index.md000066400000000000000000000052561477357142500202420ustar00rootroot00000000000000 # Test Module The `Test` module contains coroutine-friendly versions of tests macros from the [QtTest][qdoc-qtest] module. ## CMake Usage ```cmake find_package(QCoro6 COMPONENTS Test) ... target_link_libraries(my-target QCoro::Test) ``` ## QMake Usage ``` QT += QCoroTest ``` ## Test Macros The module contains a bunch of test macros that behave identically to their QtTest counterparts, the only difference being that they can be used inside a coroutine. ```cpp #include ``` * [`QCORO_COMPARE(actual, expected)`][qdoc-qcompare] * [`QCORO_EXPECT_FAIL(dataIndex, comment, mode)`][qdoc-qexpect-fail] * [`QCORO_FAIL(message)`][qdoc-qfail] * [`QCORO_SKIP(description)`][qdoc-qskip] * [`QCORO_TEST(data, testElement)`][qdoc-qtest] * [`QCORO_TRY_COMPARE(actual, expected)`][qdoc-qtry-compare] * [`QCORO_TRY_COMPARE_WITH_TIMEOUT(actual, expected, timeout)`][qdoc-qtry-compare-with-timeout] * [`QCORO_TRY_VERIFY2(condition, message)`][qdoc-qtry-verify2] * [`QCORO_TRY_VERIFY(condition)`][qdoc-qtry-verify] * [`QCORO_TRY_VERIFY2_WITH_TIMEOUT(condition, message, timeout)`][qdoc-qtry-verify2-with-timeout] * [`QCORO_TRY_VERIFY_WITH_TIMEOUT(condition, timeout)`][qdoc-qtry-verify-with-timeout] * [`QCORO_VERIFY2(condition, message)`][qdoc-qverify2] * [`QCORO_VERIFY(condition)`][qdoc-qverify] * [`QCORO_VERIFY_EXCEPTION_THROWN(expression, exceptionType)`][qdoc-qverify-exception-thrown] * [`QCORO_VERIFY_THROWS_EXCEPTION(exceptionType, ...)`][qdoc6-qverify-throws-exception] [qdoc-qtest]: https://doc.qt.io/qt-5/qttest-index.html [qdoc-qcompare]: https://doc.qt.io/qt-5/qtest.html#QCOMPARE [qdoc-qexpect-fail]: https://doc.qt.io/qt-5/qtest.html#QEXPECT_FAIL [qdoc-qfail]: https://doc.qt.io/qt-5/qtest.html#QFAIL [qdoc-qskip]: https://doc.qt.io/qt-5/qtest.html#QSKIP [qdoc-qtest]: https://doc.qt.io/qt-5/qtest.html#QTEST [qdoc-qtry-compare]: https://doc.qt.io/qt-5/qtest.html#QTRY_COMPARE [qdoc-qtry-compare-with-timeout]: https://doc.qt.io/qt-5/qtest.html#QTRY_COMPARE_WITH_TIMEOUT [qdoc-qtry-verify2]: https://doc.qt.io/qt-5/qtest.html#QTRY_VERIFY2 [qdoc-qtry-verify]: https://doc.qt.io/qt-5/qtest.html#QTRY_VERIFY [qdoc-qtry-verify2-with-timeout]: https://doc.qt.io/qt-5/qtest.html#QTRY_VERIFY2_WITH_TIMEOUT [qdoc-qtry-verify-with-timeout]: https://doc.qt.io/qt-5/qtest.html#QTRY_VERIFY_WITH_TIMEOUT [qdoc-qverify2]: https://doc.qt.io/qt-5/qtest.html#QVERIFY2 [qdoc-qverify]: https://doc.qt.io/qt-5/qtest.html#QVERIFY [qdoc-qverify-exception-thrown]: https://doc.qt.io/qt-5/qtest.html#QVERIFY_EXCEPTION_THROWN [qdoc6-qverify-throws-exception]: https://doc.qt.io/qt-6/qtest.html#QVERIFY_THROWS_EXCEPTION qcoro-0.12.0/docs/reference/websockets/000077500000000000000000000000001477357142500177735ustar00rootroot00000000000000qcoro-0.12.0/docs/reference/websockets/index.md000066400000000000000000000007421477357142500214270ustar00rootroot00000000000000 # WebSockets Module The `WebSockets` module contains coroutine-friendly wrapper for [QtWebSockets][qtdoc-websockets] classes. ## CMake Usage ``` find_package(QCoro6 COMPONENTS WebSockets) ... target_link_libraries(my-target QCoro::WebSockets) ``` ## QMake Usage ``` QT += QCoroWebSockets ``` [qtdoc-websockets]: https://doc.qt.io/qt-5/qtwebsockets-index.html qcoro-0.12.0/docs/reference/websockets/qwebsocket.md000066400000000000000000000122121477357142500224620ustar00rootroot00000000000000 # QWebSocket {{ doctable("WebSockets", "QCoroWebSocket") }} [`QWebSocket`][qtdoc-qwebsocket] provides a client socket to connect to a WebSocket server, which several asynchronous operations that could be used as coroutines. The `QCoroWebSocket` wrapper provides exactly this. Use `qCoro()` to wrap an existing instance of `QWebSocket` to become `QCoroWebSocket`: ```cpp QCoroWebSocket qCoro(QWebSocket &); QCoroWebSocket qCoro(QWebSocket *); ``` ## open() Opens connection to the WebSocket server and waits until the connection is established, or a timeout occurs. Resolves to `true` when the connection was successfully established, or `false` if the process has timed out. If the timeout is `-1`, the operation will never time out. See documentation for [`QWebSocket::open(const QUrl &)`][qtdoc-qwebsocket-open-qurl] and [`QWebSocket::open(const QNetworkRequest &)`][qtdoc-qwebsocket-open-qnetworkrequest] for details. ```cpp QCoro::Task QCoroWebSocket::open(const QUrl &url, std::chrono::milliseconds timeout); QCoro::Task QCoroWebSocket::open(const QNetworkRequest &request, std::chrono::milliseconds timeout); ``` ## ping() Sends the given payload to the server and waits for response. Returns the roundtrip time elapsed, or empty value if the operation has timed out. If the timeout is set to `-1`, the operation will never time out. See documentation for [`QWebSocket::ping()`][qtdoc-qwebsocket-ping] for details. ```cpp QCoro::Task> ping(const QByteArray &payload, std::chrono::milliseconds timeout); ``` ## binaryFrames() Returns an [asynchronous generator][qcoro-async-generator] that will yield frame data whenever they arrive. More specifically, the generator will yield a tuple of `QByteArray` containing the frame data, and a `boolean` value, indicating whether this is the last frame of a message, allowing to receive large messages in smaller frame-sized chunks. Note that the generator will never terminate, unless the socket is disconnected or unless the time elapsed between frames gets over the specified `timeout`. If the `timeout` is `-1`, the generator will wait for new frames indefinitely. See documentation for the [`QWebSocket::binaryFrameReceived()`][qtdoc-qwebsocket-binary-frame-received] signal for details. ```cpp QCoro::AsyncGenerator> QCoroWebSocket::binaryFrames(std::chrono::milliseconds timeout); ``` !!! question "Why generator instead of simply co_awaiting next frame?" It's logical to ask why does this function return a generator, rather than simply returning the received frame, the user would then go on to `co_await` the next frame etc. Unlike e.g. `QIODevice`-based classes, the `QWebSocket` is not buffered, that is any frame that arrives when no-one is connected to the `binaryFrameReceived()` signal is simply dropped and cannot be retrieved later. Therefore we need to use a generator API, which will buffer all received frames for as long as the generator object exists and provide them through the familiar iterator-like interface. Here's an example coroutine that assembles messages from incoming frames and emits a singal whenever a full message is assembled (don't use this in real code, use the `binaryMessages()` coroutine to receive complete messages). ```cpp QCoro::Task<> MessageReceived::receive() { QByteArray message; QCORO_FOREACH(const auto &[frame, isLast], qCoro(mWebSocket).binaryFrames()) { message.append(frame); if (isLast) { Q_EMIT messageAssembled(message); message.clear(); } } qDebug() << "Socket disconnected!"; } ``` ## textFrames() Behaves exactly like [`binaryFrames()`](#binaryframes), except that it expects the frame to contain text data. ```cpp QCoro::AsyncGenerator> QCoroWebSocket::textFrames(std::chrono::milliseconds timeout); ``` ## binaryMessages() Returns an [asynchronous generator][qcoro-async-generator] that will yield a binary message whenever it arrives. Note that the generator will never terminate, unless the socket is disconnected or unless the time elapsed between frames gets over the specified `timeout`. If the `timeout` is `-1`, the generator will wait for new frames indefinitely. See documentation for the [`QWebSocket::binaryMessageReceived()`][qtdoc-qwebsocket-binary-message-received] signal for details. ```cpp QCoro::AsyncGenerator QCoroWebSocket::binaryMessages(std::chrono::milliseconds timeout); ``` ## textMessages() Behaves exactly like [`binaryMessages()`](#binarymessages), except that it expects the message to be text message. ```cpp QCoro::AsyncGenerator QCoroWebSocket::textMessage(std::chrono::milliseconds timeout); ``` [qtdoc-qwebsocket]: https://doc.qt.io/qt-5/qwebsocket.html [qtdoc-qwebsocket-open-qurl]: https://doc.qt.io/qt-5/qwebsocket.html#open [qtdoc-qwebsocket-open-qnetworkrequest]: https://doc.qt.io/qt-5/qwebsocket.html#open-1 [qtdoc-qwebsocket-ping]: https://doc.qt.io/qt-5/qwebsocket.html#ping [qcoro-async-generator]: ../coro/asyncgenerator.md qcoro-0.12.0/docs/reference/websockets/qwebsocketserver.md000066400000000000000000000034241477357142500237160ustar00rootroot00000000000000 # QWebSocketServer {{ doctable("WebSockets", "QCoroWebSocketServer") }} QCoro provides a wrapper for the [`QWebSocketServer`][qtdoc-qwebsocketserver] class which allows user to asynchronously co_await incoming connections. To wrap a `QWebSocketServer` instance, use the `qCoro()` overload from the `QCoroWebSocketServer` include header. ```cpp QCoroWebSocketServer qCoro(QWebSocketServer &); QCoroWebSocketServer qCoro(QWebSocketServer *); ``` ## nextPendingConnection() Suspends the awaiter until a new incoming connection becomes available or until the specified timeout. If the specified timeout is `-1`, the operation will never time out. Returns a pointer to [`QWebSocket`][qtdoc-qwebsocket] representing the new connection, or a null pointer if the operation times out, the server is not [`listen()`][qtdoc-qwebsocketserver-listen]ining for incoming connections. ```cpp QCoro::Task QCoroWebSocketServer::nextPendingConnection(std::chrono::milliseconds timeout); ``` Note that [`pauseAccepting()`][qtdoc-qwebsocketserver-pauseAccepting] doesn't resume any awaiting coroutines. ```cpp QCoro::Task<> listen(QWebSocketServer *server) { server->listen(); while (auto socket = std::unique_ptr(co_await qCoro(server).nextPendingConnection()); socket != nullptr) { handleConnection(std::move(socket)); } } ``` [qtdoc-qwebsocketserver]: https://doc.qt.io/qt-5/qwebsocketserver.html [qtdoc-qwebsocketserver-pauseAccepting]: https://doc.qt.io/qt-5/qwebsocketserver.html#pauseAccepting [qtdoc-qwebsocketserver-listen]: https://doc.qt.io/qt-5/qwebsocketserver.html#listen [qtdoc-qwebsocket]: https://doc.qt.io/qt-5/qwebsocket.html qcoro-0.12.0/docs/release-checklist.md000066400000000000000000000017431477357142500176020ustar00rootroot00000000000000# QCoro Release Checklist 1. Ensure all CI builds for current `main` pass 1. Create Release Announcement in `docs/news/` 1. Update `docs/changelog.md` 1. Bump version in `qcoro_VERSION` in CMakeLists.txt to `X.Y.Z` 1. Commit release announcement and version bump as `QCoro release X.Y.Z` 1. Tag the release commit: `git tag -s vX.Y.Z -m "QCoro X.Y.Z"` 1. Bump version in `qcoro_VERSION` in CMakeLists.txt to `X.Y.80` 1. Commit version bump as `Bump version to X.Y.80 (X.(Y+1).0 development)` 1. Push tag and commits: `git push --tags origin main` 1. [Create release in Github](https://github.com/danvratil/qcoro/releases/new) 1. Submit PR to update QCoro in [ConanCenter](https://github.com/conan-io/conan-center-index/) 1. Submit PRs to update QCoro in [vcpkg](https://github.com/microsoft/vcpkg) 1. Copy the release announcement to my blog to publicize the release on Planet KDE 1. Post an announcement to Twitter 1. Add any extra step taken during last release and not included in this list qcoro-0.12.0/docs/stylesheets/000077500000000000000000000000001477357142500162405ustar00rootroot00000000000000qcoro-0.12.0/docs/stylesheets/doctable.css000066400000000000000000000002701477357142500205260ustar00rootroot00000000000000.doctable { width: 100% } .doctable .md-typeset__table { width: 100% } .doctable td { width: 100% } .doctable .md-typeset__table table th { vertical-align: middle } qcoro-0.12.0/examples/000077500000000000000000000000001477357142500145525ustar00rootroot00000000000000qcoro-0.12.0/examples/CMakeLists.txt000066400000000000000000000005051477357142500173120ustar00rootroot00000000000000add_subdirectory(basics) if (QCORO_WITH_QTDBUS) add_subdirectory(dbus) endif() if (QCORO_WITH_QTNETWORK) add_subdirectory(network) endif() if (QCORO_QT_HAS_COMPAT_ABI) add_subdirectory(future) endif() add_subdirectory(iodevice) add_subdirectory(timer) add_subdirectory(chained) add_subdirectory(background-task) qcoro-0.12.0/examples/background-task/000077500000000000000000000000001477357142500176315ustar00rootroot00000000000000qcoro-0.12.0/examples/background-task/CMakeLists.txt000066400000000000000000000004141477357142500223700ustar00rootroot00000000000000add_executable(background-task-example) target_sources(background-task-example PRIVATE main.cpp) target_link_libraries(background-task-example PRIVATE QCoro${QT_VERSION_MAJOR}Core Qt${QT_VERSION_MAJOR}::Core ) set_target_defaults(background-task-example) qcoro-0.12.0/examples/background-task/main.cpp000066400000000000000000000036671477357142500212750ustar00rootroot00000000000000#include #include #include "qcorotask.h" #include "qcorotimer.h" #include "qcoroiodevice.h" using namespace std::chrono_literals; // We could use std::stop_token, but it is not supported by AppleClang struct Stop { public: void requestStop() { mShouldStop = true; } bool stopRequested() const { return mShouldStop; } private: bool mShouldStop = false; }; auto backgroundTask(const Stop &stop) -> QCoro::Task<> { qDebug() << "Task: Background task started, waiting for event loop"; co_await QCoro::sleepFor(0ms); qDebug() << "Task: Event loop is running"; QFile file(QStringLiteral("/dev/stdin")); bool openResult = file.open(QIODevice::ReadOnly | QIODevice::Unbuffered); Q_UNUSED(openResult); while (!stop.stopRequested()) { qDebug() << "Task: Waiting for input..."; const auto result = co_await qCoro(file).readLine(1024, 5s); if (!result.isEmpty()) { qDebug() << "Task: Read line:" << result; } else { qDebug() << "Task: Timeout!"; } } qDebug() << "Task: Backround task stopped"; } int main(int argc, char **argv) { QCoreApplication app(argc, argv); Stop stop; auto bgTask = backgroundTask(stop); QObject::connect(&app, &QCoreApplication::aboutToQuit, [&stop] { qDebug() << "App: Requesting background task to stop"; stop.requestStop(); }); QTimer::singleShot(500ms, &app, []() { qDebug() << "App: Stopping application"; QCoreApplication::instance()->quit(); }); // Run the app qDebug() << "App: Starting application event loop"; const auto result = app.exec(); qDebug() << "App: Application event loop stopped"; // Wait for the background task to complete qDebug() << "App: Waiting for background task to complete"; QCoro::waitFor(bgTask); qDebug() << "App: Background task completed"; return result; } qcoro-0.12.0/examples/basics/000077500000000000000000000000001477357142500160165ustar00rootroot00000000000000qcoro-0.12.0/examples/basics/CMakeLists.txt000066400000000000000000000005601477357142500205570ustar00rootroot00000000000000add_executable(await-sync-string await-sync-string.cpp) target_link_libraries(await-sync-string QCoro${QT_VERSION_MAJOR}::Coro) set_target_defaults(await-sync-string) add_executable(await-async-string await-async-string.cpp) target_link_libraries(await-async-string QCoro${QT_VERSION_MAJOR}::Coro Qt${QT_VERSION_MAJOR}::Core) set_target_defaults(await-async-string) qcoro-0.12.0/examples/basics/await-async-string.cpp000066400000000000000000000163241477357142500222540ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcoro/coroutine.h" #include #include #include #include #include #include #include using namespace std::chrono_literals; class FutureString : public QObject { Q_OBJECT public: explicit FutureString(const QString &str) : mStr(str) { QTimer::singleShot(1s, this, [this]() { mReady = true; Q_EMIT ready(); }); } bool isReady() const { return mReady; } QString result() const { return mStr; } Q_SIGNALS: void ready(); private: bool mReady = false; QString mStr; }; // Awaiter is a concept that provides the await_* methods below, which are used by the // co_await expression. // Type is Awaitable if it supports the `co_await` operator. // // When compiler sees a `co_await `, it first tries to obtain an Awaitable type for // the expression result result type: // - first by checking if the current coroutine's promise type has `await_transform()` // that for given type returns an Awaitable // - if it does not have await_transform, it treats the result type as awaitable. // Thus, if the current promise type doesn't have compatible `await_transform()` and the // type itself is not Awaitable, it cannot be `co_await`ed. // // If the Awaitable object has `operator co_await` overload, it calls it to obtain the // Awaiter object. Otherwise the Awaitable object is used as an Awaiter. // class FutureStringAwaiter { public: explicit FutureStringAwaiter(const std::shared_ptr value) noexcept : mFuture(value) { std::cout << "FutureStringAwaiter constructed." << std::endl; } ~FutureStringAwaiter() { std::cout << "FutureStringAwaiter destroyed." << std::endl; } // Called by compiler when starting co_await to check whether the awaited object is by any // chance already ready, so that we could avoid the suspend-resume dance. bool await_ready() noexcept { std::cout << "FutureStringAwaiter::await_ready() called." << std::endl; return mFuture->isReady(); } // Called to tell us that the awaiting coroutine was suspended. // We use the awaitingCoroutine handle to resume the suspended coroutine once the // co_awaited coroutine is finished. void await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept { std::cout << "FutureStringAwaiter::await_suspend() called." << std::endl; QObject::connect(mFuture.get(), &FutureString::ready, [awaitingCoroutine]() mutable { awaitingCoroutine.resume(); }); } // Called when the co_awaiting coroutine is resumed. Returns result of the // co_awaited expression. QString await_resume() noexcept { std::cout << "FutureStringAwaiter::await_resume() called." << std::endl; return mFuture->result(); } private: std::shared_ptr mFuture; }; class FutureStringAwaitable { public: FutureStringAwaitable(const std::shared_ptr value) noexcept : mFuture(value) { std::cout << "FutureStringAwaitable constructed." << std::endl; } ~FutureStringAwaitable() { std::cout << "FutureStringAwaitable destroyed." << std::endl; } FutureStringAwaiter operator co_await() { std::cout << "FutureStringAwaitable::operator co_await() called." << std::endl; return FutureStringAwaiter{mFuture}; } private: std::shared_ptr mFuture; }; class VoidPromise { public: explicit VoidPromise() { std::cout << "VoidPromise constructed." << std::endl; } ~VoidPromise() { std::cout << "VoidPromise destroyed." << std::endl; } struct promise_type { explicit promise_type() { std::cout << "VoidPromise::promise_type constructed." << std::endl; } ~promise_type() { std::cout << "VoidPromise::promise_type destroyed." << std::endl; } // Says whether the coroutine body should be executed immediately (`suspend_never`) // or whether it should be executed only once the coroutine is co_awaited. std::suspend_never initial_suspend() const noexcept { return {}; } // Says whether the coroutine should be suspended after returning a result // (`suspend_always`) or whether it should just end and the frame pointer and everything // should be destroyed. std::suspend_never final_suspend() const noexcept { return {}; } // Called by the compiler during initial coroutine setup to obtain the object that // will be returned from the coroutine when it is suspended. // Sicne this is a promise type for VoidPromise, we return a VoidPromise. VoidPromise get_return_object() { std::cout << "VoidPromise::get_return_object() called." << std::endl; return VoidPromise(); } // Called by the compiler when an exception propagates from the coroutine. // Alternatively, we could declare `set_exception()` which the compiler would // call instead to let us handle the exception (e.g. propagate it) void unhandled_exception() { std::terminate(); } // The result of the promise. Since our promise is void, we must implement `return_void()`. // If our promise would be returning a value, we would have to implement `return_value()` // instead. void return_void() const noexcept {}; FutureStringAwaitable await_transform(const std::shared_ptr &future) { std::cout << "VoidPromise::await_transform called." << std::endl; return FutureStringAwaitable{future}; } }; }; std::shared_ptr regularFunction() { return std::make_shared(QStringLiteral("Hello World!")); } // This function co_awaits, therefore it's a co-routine and must // have a promise type to return to the caller. VoidPromise myCoroutine() { // 1. Compiler creates a new coroutine frame `f` // 2. Compiler obtains a return object from the promise. // The promise is of type `std::coroutine_traits::promise_type`, // which is `CurrentFunctionReturnType::promise_type` (if there is no specialization for // `std::coroutine_traits`) // 3. Compiler starts execution of the coroutine body by calling `resume()` on the // current coroutine's std::coroutine_handle (obtained from the promise by // `std::coroutine_handlepromise)>::from_promise(f->promise) std::cout << "myCoroutine() started." << std::endl; const auto result = co_await regularFunction(); std::cout << "Result successfully co_await-ed: " << result.toStdString() << std::endl; qApp->quit(); } int main(int argc, char **argv) { QCoreApplication app(argc, argv); QMetaObject::invokeMethod(&app, myCoroutine); QTimer t; QObject::connect(&t, &QTimer::timeout, &app, []() { std::cout << "Tick" << std::endl; }); t.start(100ms); return app.exec(); return 0; } #include "await-async-string.moc" qcoro-0.12.0/examples/basics/await-sync-string.cpp000066400000000000000000000132311477357142500221050ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcoro/coroutine.h" #include #include // Awaiter is a concept that provides the await_* methods below, which are used by the // co_await expression. // Type is Awaitable if it supports the `co_await` operator. // // When compiler sees a `co_await `, it first tries to obtain an Awaitable type for // the expression result result type: // - first by checking if the current coroutine's promise type has `await_transform()` // that for given type returns an Awaitable // - if it does not have await_transform, it treats the result type as awaitable. // Thus, if the current promise type doesn't have compatible `await_transform()` and the // type itself is not Awaitable, it cannot be `co_await`ed. // // If the Awaitable object has `operator co_await` overload, it calls it to obtain the // Awaiter object. Otherwise the Awaitable object is used as an Awaiter. // class StringAwaiter { public: explicit StringAwaiter(const std::string &value) noexcept : mValue(value) { std::cout << "StringAwaiter constructed with value '" << value << "'." << std::endl; } ~StringAwaiter() { std::cout << "StringAwaiter destroyed." << std::endl; } bool await_ready() noexcept { std::cout << "StringAwaiter::await_ready() called." << std::endl; return false; } void await_suspend(std::coroutine_handle<>) noexcept { std::cout << "StringAwaiter::await_suspend() called." << std::endl; } std::string await_resume() noexcept { std::cout << "StringAwaiter::await_resume() called." << std::endl; return mValue; } private: std::string mValue; }; class StringAwaitable { public: StringAwaitable(std::string str) noexcept : mStr(std::move(str)) { std::cout << "StringAwaitable constructored with value '" << mStr << "'." << std::endl; } ~StringAwaitable() { std::cout << "StringAwaitable destroyed." << std::endl; } StringAwaiter operator co_await() { std::cout << "StringAwaitable::operator co_await() called." << std::endl; return StringAwaiter{mStr}; } private: std::string mStr; }; class VoidPromise { public: explicit VoidPromise() { std::cout << "VoidPromise constructed." << std::endl; } ~VoidPromise() { std::cout << "VoidPromise destroyed." << std::endl; } struct promise_type { explicit promise_type() { std::cout << "VoidPromise::promise_type constructed." << std::endl; } ~promise_type() { std::cout << "VoidPromise::promise_type destroyed." << std::endl; } // Says whether the coroutine body should be executed immediately (`suspend_never`) // or whether it should be executed only once the coroutine is co_awaited. std::suspend_never initial_suspend() const noexcept { return {}; } // Says whether the coroutine should be suspended after returning a result // (`suspend_always`) or whether it should just end and the frame pointer and everything // should be destroyed. std::suspend_never final_suspend() const noexcept { return {}; } // Called by the compiler during initial coroutine setup to obtain the object that // will be returned from the coroutine when it is suspended. // Sicne this is a promise type for VoidPromise, we return a VoidPromise. VoidPromise get_return_object() { std::cout << "VoidPromise::get_return_object() called." << std::endl; return VoidPromise(); } // Called by the compiler when an exception propagates from the coroutine. // Alternatively, we could declare `set_exception()` which the compiler would // call instead to let us handle the exception (e.g. propagate it) void unhandled_exception() { std::terminate(); } // The result of the promise. Since our promise is void, we must implement `return_void()`. // If our promise would be returning a value, we would have to implement `return_value()` // instead. void return_void() const noexcept {}; StringAwaitable await_transform(std::string str) { std::cout << "VoidPromise::await_transform for string '" << str << "' called." << std::endl; return StringAwaitable{std::move(str)}; } }; }; std::string regularFunction() { return "Hello World!"; } // This function co_awaits, therefore it's a co-routine and must // have a promise type to return to the caller. VoidPromise myCoroutine() { // 1. Compiler creates a new coroutine frame `f` // 2. Compiler obtains a return object from the promise. // The promise is of type `std::coroutine_traits::promise_type`, // which is `CurrentFunctionReturnType::promise_type` (if there is no specialization for // `std::coroutine_traits`) // 3. Compiler starts execution of the coroutine body by calling `resume()` on the // current coroutine's std::coroutine_handle (obtained from the promise by // `std::coroutine_handlepromise)>::from_promise(f->promise) std::cout << "myCoroutine() started." << std::endl; const auto result = co_await regularFunction(); std::cout << "Result successfully co_await-ed: " << result << std::endl; } int main() { std::cout << "Calling myCoroutine() from main()." << std::endl; myCoroutine(); std::cout << "Returned from myCoroutine() to main()." << std::endl; return 0; } qcoro-0.12.0/examples/chained/000077500000000000000000000000001477357142500161455ustar00rootroot00000000000000qcoro-0.12.0/examples/chained/CMakeLists.txt000066400000000000000000000002701477357142500207040ustar00rootroot00000000000000add_executable(chained-example main.cpp) target_link_libraries(chained-example QCoro${QT_VERSION_MAJOR}Core Qt${QT_VERSION_MAJOR}::Core ) set_target_defaults(chained-example) qcoro-0.12.0/examples/chained/main.cpp000066400000000000000000000031601477357142500175750ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcorotask.h" #include "qcorotimer.h" #include #include #include #include using namespace std::chrono_literals; QCoro::Task generateRandomString() { std::cout << "GenerateRandomString started" << std::endl; QTimer timer; timer.start(1s); std::cout << "GenerateRandomString \"generating\"..." << std::endl; co_await timer; std::cout << "GenerateRandomString finished \"generating\"" << std::endl; std::cout << "GenerateRandomString co_returning to caller" << std::endl; co_return QStringLiteral("RandomString!"); } QCoro::Task generateRandomNumber() { std::cout << "GenerateRandomNumber started" << std::endl; std::cout << "GenerateRandomNumber co_awaiting on generateRandomString()" << std::endl; const QString string = co_await generateRandomString(); std::cout << "GenerateRandomNumber successfully co_awaited on generateRandomString() and " "co_returns result" << std::endl; co_return string.size(); } QCoro::Task<> logRandomNumber() { std::cout << "LogRandomNumber started" << std::endl; std::cout << "LogRandomNumber co_awaiting on generateRandomNumber()" << std::endl; const int number = co_await generateRandomNumber(); std::cout << "Random number for today is: " << number << std::endl; qApp->quit(); } int main(int argc, char **argv) { QCoreApplication app{argc, argv}; QTimer::singleShot(0, qApp, logRandomNumber); return app.exec(); } qcoro-0.12.0/examples/dbus/000077500000000000000000000000001477357142500155075ustar00rootroot00000000000000qcoro-0.12.0/examples/dbus/CMakeLists.txt000066400000000000000000000001321477357142500202430ustar00rootroot00000000000000add_subdirectory(common) add_subdirectory(regular-blocking) add_subdirectory(coroutine) qcoro-0.12.0/examples/dbus/common/000077500000000000000000000000001477357142500167775ustar00rootroot00000000000000qcoro-0.12.0/examples/dbus/common/CMakeLists.txt000066400000000000000000000016011477357142500215350ustar00rootroot00000000000000 # Build the dbus-server as a stand-alone executable to workaround QTBUG-92107 add_executable(dbusserver dbusserver.cpp) set_target_properties(dbusserver PROPERTIES COMPILE_DEFINITIONS STANDALONE ) target_link_libraries(dbusserver Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::DBus ) set_target_defaults(dbusserver) #-----------------------------------------------------# set(common_SRCS dbusserver.cpp ) add_library(examples-dbus-common STATIC ${common_SRCS}) set_target_properties(examples-dbus-common PROPERTIES COMPILE_DEFINITIONS SERVER_EXEC_PATH=\"$\") target_include_directories(examples-dbus-common INTERFACE $ ) target_link_libraries(examples-dbus-common PUBLIC Qt${QT_VERSION_MAJOR}::Core PRIVATE Qt${QT_VERSION_MAJOR}::DBus ) set_target_defaults(examples-dbus-common) qcoro-0.12.0/examples/dbus/common/dbusserver.cpp000066400000000000000000000030651477357142500216730ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "dbusserver.h" #include #include #include #include #include const QString DBusServer::serviceName = QStringLiteral("org.kde.qoro.dbustest"); const QString DBusServer::objectPath = QStringLiteral("/"); const QString DBusServer::interfaceName = QStringLiteral("org.kde.qoro.dbuserver"); DBusServer::DBusServer() { qInfo() << "DBusServer started"; auto bus = QDBusConnection::sessionBus(); bus.registerService(serviceName); bus.registerObject(objectPath, interfaceName, this, QDBusConnection::ExportAllSlots); } QString DBusServer::blockingPing(int seconds) const { qInfo() << "S: Received ping request..."; std::this_thread::sleep_for(std::chrono::seconds{seconds}); qInfo() << "S: sending PONG response"; return QStringLiteral("PONG!"); } std::unique_ptr DBusServer::runStadaloneServer() { #ifdef SERVER_EXEC_PATH auto process = std::make_unique(); process->setProcessChannelMode(QProcess::ForwardedChannels); process->start(QStringLiteral(SERVER_EXEC_PATH), {}, QIODevice::ReadOnly); process->waitForStarted(); if (process->state() != QProcess::Running) { qCritical() << "Failed to start server process:" << process->error(); } return process; #else return {}; #endif } #ifdef STANDALONE int main(int argc, char **argv) { QCoreApplication app(argc, argv); DBusServer server; return app.exec(); } #endif qcoro-0.12.0/examples/dbus/common/dbusserver.h000066400000000000000000000007621477357142500213410ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include #include #include class DBusServer : public QObject { Q_OBJECT public: static const QString serviceName; static const QString objectPath; static const QString interfaceName; explicit DBusServer(); static std::unique_ptr runStadaloneServer(); public Q_SLOTS: QString blockingPing(int seconds) const; }; qcoro-0.12.0/examples/dbus/coroutine/000077500000000000000000000000001477357142500175165ustar00rootroot00000000000000qcoro-0.12.0/examples/dbus/coroutine/CMakeLists.txt000066400000000000000000000004521477357142500222570ustar00rootroot00000000000000set(dbustest_SRCS main.cpp ) add_executable(dbustest-coro ${dbustest_SRCS}) target_link_libraries(dbustest-coro QCoro${QT_VERSION_MAJOR}DBus examples-dbus-common Threads::Threads Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::DBus ) set_target_defaults(dbustest-coro) qcoro-0.12.0/examples/dbus/coroutine/main.cpp000066400000000000000000000030421477357142500211450ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "coroutine.h" #include "qcorodbus.h" #include "qcorotask.h" #include "common/dbusserver.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace std::chrono_literals; QCoro::Task<> dbusWorker() { auto bus = QDBusConnection::sessionBus(); auto iface = QDBusInterface{DBusServer::serviceName, DBusServer::objectPath, DBusServer::interfaceName, bus}; qInfo() << "Sending PING"; QDBusReply response = co_await iface.asyncCall(QStringLiteral("blockingPing"), 1); if (const auto &err = response.error(); err.isValid()) { qWarning() << "DBus call failed:" << err.message(); } qInfo() << "Received response:" << response.value(); } int main(int argc, char **argv) { QCoreApplication app(argc, argv); auto process = DBusServer::runStadaloneServer(); QTimer tickTimer; QObject::connect(&tickTimer, &QTimer::timeout, &app, []() { std::cout << QDateTime::currentDateTime().toString(Qt::ISODateWithMs).toStdString() << " Tick!" << std::endl; }); tickTimer.start(400ms); QTimer dbusTimer; QObject::connect(&dbusTimer, &QTimer::timeout, &app, dbusWorker); dbusTimer.start(2s); return app.exec(); } qcoro-0.12.0/examples/dbus/regular-blocking/000077500000000000000000000000001477357142500207365ustar00rootroot00000000000000qcoro-0.12.0/examples/dbus/regular-blocking/CMakeLists.txt000066400000000000000000000003721477357142500235000ustar00rootroot00000000000000set(dbustest_SRCS main.cpp ) add_executable(dbustest ${dbustest_SRCS}) target_link_libraries(dbustest examples-dbus-common Threads::Threads Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::DBus ) set_target_defaults(dbustest) qcoro-0.12.0/examples/dbus/regular-blocking/main.cpp000066400000000000000000000026261477357142500223740ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "common/dbusserver.h" #include #include #include #include #include #include #include #include #include #include using namespace std::chrono_literals; void dbusWorker() { auto bus = QDBusConnection::sessionBus(); auto iface = QDBusInterface{DBusServer::serviceName, DBusServer::objectPath, DBusServer::interfaceName, bus}; qInfo() << "Sending PING"; QDBusReply response = iface.call(QStringLiteral("blockingPing"), 1); if (const auto &err = response.error(); err.isValid()) { qWarning() << "DBus call failed:" << err.message(); } qInfo() << "Received response:" << response.value(); } int main(int argc, char **argv) { QCoreApplication app(argc, argv); auto process = DBusServer::runStadaloneServer(); QTimer tickTimer; QObject::connect(&tickTimer, &QTimer::timeout, &app, []() { std::cout << QDateTime::currentDateTime().toString(Qt::ISODateWithMs).toStdString() << " Tick!" << std::endl; }); tickTimer.start(200ms); QTimer dbusTimer; QObject::connect(&dbusTimer, &QTimer::timeout, &app, dbusWorker); dbusTimer.start(2s); return app.exec(); } qcoro-0.12.0/examples/future/000077500000000000000000000000001477357142500160645ustar00rootroot00000000000000qcoro-0.12.0/examples/future/CMakeLists.txt000066400000000000000000000003331477357142500206230ustar00rootroot00000000000000add_executable(future-example main.cpp) target_link_libraries(future-example QCoro${QT_VERSION_MAJOR}Core Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Concurrent ) set_target_defaults(future-example) qcoro-0.12.0/examples/future/main.cpp000066400000000000000000000021331477357142500175130ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcorofuture.h" #include #include #include #include #include QCoro::Task<> startTask() { const auto data = co_await QtConcurrent::run([]() { QVector data; std::random_device rd{}; std::mt19937 gen{rd()}; data.reserve(10'000'000); for (int i = 0; i < 10'000'000; ++i) { data.push_back(gen()); } return data; }); std::cout << "Generated " << data.size() << " random numbers" << std::endl; const auto sum = co_await QtConcurrent::filteredReduced( data, [](const auto &) { return true; }, [](std::uint64_t &interm, std::uint64_t val) { interm += val; }, QtConcurrent::UnorderedReduce); std::cout << "Calculated result: " << sum << std::endl; qApp->quit(); } int main(int argc, char **argv) { QCoreApplication app(argc, argv); QTimer::singleShot(0, startTask); return app.exec(); } qcoro-0.12.0/examples/iodevice/000077500000000000000000000000001477357142500163415ustar00rootroot00000000000000qcoro-0.12.0/examples/iodevice/CMakeLists.txt000066400000000000000000000004021477357142500210750ustar00rootroot00000000000000add_executable(iodevice-example main.cpp) target_link_libraries(iodevice-example QCoro${QT_VERSION_MAJOR}Core QCoro${QT_VERSION_MAJOR}Network Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Network ) set_target_defaults(iodevice-example) qcoro-0.12.0/examples/iodevice/main.cpp000066400000000000000000000031411477357142500177700ustar00rootroot00000000000000#include "qcoroiodevice.h" #include "qcorotask.h" #include #include #include #include #include #include #include using namespace std::chrono_literals; class Server : public QObject { Q_OBJECT public: explicit Server(QHostAddress addr, uint16_t port) { mServer.listen(addr, port); connect(&mServer, &QTcpServer::newConnection, this, &Server::handleConnection); } private Q_SLOTS: QCoro::Task<> handleConnection() { auto socket = mServer.nextPendingConnection(); while (socket->isOpen()) { const auto data = co_await socket; socket->write("PONG: " + data); } } private: QTcpServer mServer; }; class Client : public QObject { Q_OBJECT public: explicit Client(QHostAddress addr, uint16_t port) { mSocket.connectToHost(addr, port); connect(&mTimer, &QTimer::timeout, this, &Client::sendPing); mTimer.start(300ms); } private Q_SLOTS: QCoro::Task<> sendPing() { std::cout << "Sending ping..." << std::endl; mSocket.write(QByteArray("PING #") + QByteArray::number(++mPing)); const auto response = co_await mSocket; std::cout << "Received pong: " << response.constData() << std::endl; } private: int mPing = 0; QTcpSocket mSocket; QTimer mTimer; }; int main(int argc, char **argv) { QCoreApplication app{argc, argv}; Server server{QHostAddress::LocalHost, 6666}; Client client{QHostAddress::LocalHost, 6666}; return app.exec(); } #include "main.moc" qcoro-0.12.0/examples/network/000077500000000000000000000000001477357142500162435ustar00rootroot00000000000000qcoro-0.12.0/examples/network/CMakeLists.txt000066400000000000000000000004021477357142500207770ustar00rootroot00000000000000add_executable(network-example main.cpp) target_link_libraries(network-example QCoro${QT_VERSION_MAJOR}Network Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network ) set_target_defaults(network-example) qcoro-0.12.0/examples/network/main.cpp000066400000000000000000000046001477357142500176730ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcoro/network/qcoronetworkreply.h" #include "qcoro/qcorotask.h" #include #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion" // QSizePolicy seems problematic... #endif // __clang__ #include #ifdef __clang__ #pragma clang diagnostic pop #endif // __clang__ #include #include #include #include #include #include #include #include // Programming langauges static const QUrl wikiUrl = QUrl{QStringLiteral("https://www.wikidata.org/wiki/Special:EntityData/Q9143.json")}; class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow() { mPb = new QProgressBar(); mPb->setVisible(false); mPb->setMinimumWidth(200); mPb->setMinimum(0); mPb->setMaximum(0); mBtn = new QPushButton(tr("Start Download")); connect(mBtn, &QPushButton::clicked, this, &MainWindow::start); auto *vbox = new QVBoxLayout(); vbox->addStretch(1); vbox->addWidget(mPb); vbox->addWidget(mBtn); vbox->addStretch(1); auto *hbox = new QHBoxLayout(); hbox->addStretch(1); hbox->addLayout(vbox); hbox->addStretch(1); QWidget *w = new QWidget; w->setLayout(hbox); setCentralWidget(w); } private Q_SLOTS: QCoro::Task<> start() { mPb->setVisible(true); mBtn->setEnabled(false); mBtn->setText(tr("Downloading ...")); std::unique_ptr reply(co_await mNam.get(QNetworkRequest{wikiUrl})); if (reply->error()) { QMessageBox::warning( this, tr("Network request error"), tr("Error occured during network request. Error code: %1").arg(reply->error())); co_return; } mPb->setVisible(false); mBtn->setEnabled(true); mBtn->setText(tr("Done, download again")); } private: QNetworkAccessManager mNam; QPushButton *mBtn = {}; QProgressBar *mPb = {}; }; int main(int argc, char **argv) { QApplication app(argc, argv); MainWindow window; window.showNormal(); return app.exec(); } #include "main.moc" qcoro-0.12.0/examples/timer/000077500000000000000000000000001477357142500156725ustar00rootroot00000000000000qcoro-0.12.0/examples/timer/CMakeLists.txt000066400000000000000000000002621477357142500204320ustar00rootroot00000000000000add_executable(timer-example main.cpp) target_link_libraries(timer-example QCoro${QT_VERSION_MAJOR}Core Qt${QT_VERSION_MAJOR}::Core ) set_target_defaults(timer-example) qcoro-0.12.0/examples/timer/main.cpp000066400000000000000000000017521477357142500173270ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcorotask.h" #include "qcorotimer.h" #include #include #include #include #include using namespace std::chrono_literals; QCoro::Task<> runMainTimer() { std::cout << "runMainTimer started" << std::endl; QTimer timer; timer.setInterval(2s); timer.start(); std::cout << "Waiting for main timer..." << std::endl; co_await timer; std::cout << "Main timer ticked!" << std::endl; qApp->quit(); } int main(int argc, char **argv) { QCoreApplication app{argc, argv}; QTimer ticker; QObject::connect(&ticker, &QTimer::timeout, &app, []() { std::cout << QDateTime::currentDateTime().toString(Qt::ISODateWithMs).toStdString() << " Secondary timer tick!" << std::endl; }); ticker.start(200ms); QTimer::singleShot(0, runMainTimer); return app.exec(); } qcoro-0.12.0/mkdocs.yml000066400000000000000000000103521477357142500147400ustar00rootroot00000000000000site_name: QCoro site_description: QCoro is a C++ framework for using coroutines with Qt site_author: Daniel Vrátil site_url: https://qcoro.dev/ copyright: Copyright © Daniel Vrátil, all contents published under GNU FDL 1.3, unless stated otherwise. repo_url: https://github.com/qcoro/qcoro repo_name: 'QCoro on GitHub' edit_uri: 'https://github.com/qcoro/qcoro/edit/main/docs/' theme: name: material custom_dir: docs/overrides logo: assets/qcoro.svg icon: repo: fontawesome/brands/github features: - navigation.expand - navigation.tracking - navigation.tabs - navigation.tabs.sticky - navigation.indexes palette: # Palette toggle for automatic mode - media: "(prefers-color-scheme: normal)" primary: teal accent: green toggle: icon: material/brightness-auto name: Switch to light mode # Palette toggle for light mode - media: "(prefers-color-scheme: light)" scheme: default primary: teal accent: green toggle: icon: material/brightness-7 name: Switch to dark mode # Palette toggle for dark mode - media: "(prefers-color-scheme: dark)" scheme: slate primary: teal accent: green toggle: icon: material/brightness-4 name: Switch to system preference markdown_extensions: - pymdownx.highlight - pymdownx.superfences - pymdownx.inlinehilite - pymdownx.extra - admonition plugins: - search: separator: '[\s\-]+|::' - include-markdown - blogging: dirs: - news sort: from: new by: creation time_format: "%B %d, %Y" meta_time_format: "%Y-%m-%d" locale: en theme: name: button options: plain_button: true - rss: match_path: news/.* date_from_meta: as_creation: date categories: - categories - tags - macros: module_name: docs/macros - privacy extra_css: - stylesheets/doctable.css extra: social: - icon: fontawesome/brands/twitter link: https://twitter.com/danvratil - icon: fontawesome/brands/mastodon link: https://fosstodon.org/@danvratil - icon: fontawesome/brands/github link: https://github.com/danvratil nav: - Home: index.md - Building and Using QCoro: building-and-using.md - Coroutines: - Qt vs. co_await: coroutines/qt-vs-coawait.md - co_await Explained: coroutines/coawait.md - Further Reading: coroutines/reading.md - News: news.md - Reference: - Coro: - reference/coro/index.md - QCoro::Task<T>: reference/coro/task.md - QCoro::LazyTask<T>: reference/coro/lazytask.md - QCoro::coro(): reference/coro/coro.md - QCoro::Generator<T>: reference/coro/generator.md - QCoro::AsyncGenerator<T>: reference/coro/asyncgenerator.md - Core: - reference/core/index.md - Qt Signals: reference/core/signals.md - QFuture: reference/core/qfuture.md - QIODevice: reference/core/qiodevice.md - QProcess: reference/core/qprocess.md - QThread: reference/core/qthread.md - QTimer: reference/core/qtimer.md - Network: - reference/network/index.md - QAbstractSocket: reference/network/qabstractsocket.md - QLocalSocket: reference/network/qlocalsocket.md - QNetworkReply: reference/network/qnetworkreply.md - QTcpServer: reference/network/qtcpserver.md - DBus: - reference/dbus/index.md - QDBusPendingCall: reference/dbus/qdbuspendingcall.md - QDBusPendingReply: reference/dbus/qdbuspendingreply.md - WebSockets: - reference/websockets/index.md - QWebSocket: reference/websockets/qwebsocket.md - QWebSocketServer: reference/websockets/qwebsocketserver.md - Quick: - reference/quick/index.md - QCoro::ImageProvider: reference/quick/imageprovider.md - Qml: - reference/qml/index.md - QCoro::QmlTask: reference/qml/qmltask.md - Test: - reference/test/index.md - Changelog: changelog.md - About: - License: about/license.md qcoro-0.12.0/qcoro/000077500000000000000000000000001477357142500140575ustar00rootroot00000000000000qcoro-0.12.0/qcoro/CMakeLists.txt000066400000000000000000000046611477357142500166260ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2021 Daniel Vrátil # # SPDX-License-Identifier: MIT # Custom includes include(GenerateHeaders) include(AddQCoroLibrary) include(CMakePackageConfigHelpers) add_qcoro_library( NAME Coro INTERFACE NO_CMAKE_CONFIG INCLUDEDIR Coro CAMELCASE_HEADERS QCoro QCoroAsyncGenerator QCoroFwd QCoroGenerator QCoroLazyTask QCoroTask HEADERS concepts_p.h coroutine.h macros_p.h waitoperationbase_p.h impl/connect.h impl/lazytask.h impl/task.h impl/taskawaiterbase.h impl/taskbase.h impl/taskfinalsuspend.h impl/taskpromise.h impl/taskpromisebase.h impl/waitfor.h QT_LINK_LIBRARIES INTERFACE Core ) if (NOT QCORO_DISABLE_DEPRECATED_TASK_H) # Install Task conditionally generate_headers( task_cc_HEADERS HEADER_NAMES Task OUTPUT_DIR QCoro ORIGINAL_HEADERS_VAR task_lc_HEADERS ) install( FILES ${task_lc_HEADERS} DESTINATION ${QCORO_INSTALL_INCLUDEDIR}/qcoro/ COMPONENT Devel ) install( FILES ${task_cc_HEADERS} DESTINATION ${QCORO_INSTALL_INCLUDEDIR}/QCoro/ COMPONENT Devel ) endif() # The QCoroCoroConfig includes the QCoroMacros.cmake so we can't # use the standard file generated by add_qcoro_library. configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/QCoroCoroConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/QCoro${QT_VERSION_MAJOR}CoroConfig.cmake" INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/QCoro${QT_VERSION_MAJOR}Coro PATH_VARS CMAKE_INSTALL_INCLUDEDIR ) write_basic_package_version_file( "${CMAKE_CURRENT_BINARY_DIR}/QCoro${QT_VERSION_MAJOR}CoroConfigVersion.cmake" VERSION ${qcoro_VERSION} COMPATIBILITY SameMajorVersion ) # Install the extra macros file install( FILES "QCoroMacros.cmake" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${QCORO_TARGET_PREFIX}Coro/" ) ############################ add_subdirectory(core) if (QCORO_WITH_QTDBUS) add_subdirectory(dbus) endif() if (QCORO_WITH_QTNETWORK) add_subdirectory(network) endif() if (QCORO_WITH_QTWEBSOCKETS) add_subdirectory(websockets) endif() if (QCORO_WITH_QTQUICK) add_subdirectory(quick) endif() if (QCORO_WITH_QML) add_subdirectory(qml) endif() if (QCORO_WITH_QTTEST) add_subdirectory(test) endif() qcoro-0.12.0/qcoro/QCoroCoroConfig.cmake.in000066400000000000000000000010301477357142500204540ustar00rootroot00000000000000@PACKAGE_INIT@ include(CMakeFindDependencyMacro) find_dependency(Qt@QT_VERSION_MAJOR@Core) include("${CMAKE_CURRENT_LIST_DIR}/QCoro@QT_VERSION_MAJOR@CoroTargets.cmake") # Custom macros include("${CMAKE_CURRENT_LIST_DIR}/QCoroMacros.cmake") # Versionless target, for compatiblity with Qt6 if (TARGET QCoro@QT_VERSION_MAJOR@::Coro AND NOT TARGET QCoro::Coro) add_library(QCoro::Coro INTERFACE IMPORTED) set_target_properties(QCoro::Coro PROPERTIES INTERFACE_LINK_LIBRARIES "QCoro@QT_VERSION_MAJOR@::Coro" ) endif() qcoro-0.12.0/qcoro/QCoroMacros.cmake000066400000000000000000000014331477357142500172520ustar00rootroot00000000000000macro(qcoro_enable_coroutines) if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") # GCC set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcoroutines") elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") if (MSVC) # clang-cl behaves like MSVC and enables coroutines automatically when C++20 is enabled else() if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "16") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcoroutines-ts") endif() endif() elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") # MSVC auto-enables coroutine support when C++20 is enabled else() message(FATAL_ERROR "Compiler ${CMAKE_CXX_COMPILER_ID} is not currently supported.") endif() endmacro() qcoro-0.12.0/qcoro/concepts_p.h000066400000000000000000000012501477357142500163630ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #ifndef Q_MOC_RUN #include #if defined(__clang__) // Sadly, libc++ doesn't currently implement any concepts, so // we need to implement them ourselves. namespace QCoro::concepts { template concept destructible = std::is_nothrow_destructible_v; template concept constructible_from = destructible && std::is_constructible_v; } // namespace QCoro::concepts #else namespace QCoro::concepts { using namespace std; } // namespace QCoro::concepts #endif // clang #endif // Q_MOC_RUN qcoro-0.12.0/qcoro/core/000077500000000000000000000000001477357142500150075ustar00rootroot00000000000000qcoro-0.12.0/qcoro/core/CMakeLists.txt000066400000000000000000000011101477357142500175400ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2021 Daniel Vrátil # # SPDX-License-Identifier: MIT add_qcoro_library( NAME Core INCLUDEDIR Core SOURCES qcoroiodevice.cpp qcoroiodevice_p.cpp qcoroprocess.cpp qcorothread.cpp qcorotimer.cpp CAMELCASE_HEADERS QCoroCore QCoroIODevice QCoroProcess QCoroSignal QCoroThread QCoroTimer QCoroFuture HEADERS impl/isqprivatesignal.h QT_LINK_LIBRARIES PUBLIC Core QCORO_LINK_LIBRARIES PUBLIC Coro ) qcoro-0.12.0/qcoro/core/impl/000077500000000000000000000000001477357142500157505ustar00rootroot00000000000000qcoro-0.12.0/qcoro/core/impl/isqprivatesignal.h000066400000000000000000000057341477357142500215170ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include #include #if defined(__cpp_lib_source_location) #include #endif namespace QCoro::detail { /** * A whacky helper to detect whether T is a QPrivateSignal type. * * The problem with QPrivateSignal is that it's a struct that's private * to each QObject-derived class, so we can't simply do std::same_as_v * because we cannot access the private QPrivateSignal struct from here. * * The only solution I could come up with abuses std::source_location * to get a string with name of the function which also shows what T is, * and then check whether the string contains "::QPrivateSignal" substring * in the right place. The whole check is compile-time, so there's no runtime * overhead. * * Unfortunately the output format of std::source_location::function_name() is * implementation-specific, so we need to handle each compiler separately. This * can cause truble in the future if an implementation changes the output format. **/ template struct is_qprivatesignal { private: static constexpr std::string_view qprivatesignal = "QPrivateSignal"; static constexpr auto functionName() noexcept { #if defined(_MSC_VER) // While MSVC does support std::source_location, std::source_location::function_name() // returns only the name of the function ("functionName"), but we need the fully qualified // name, which MSVC-specific __FUNCSIG__ macro gives us return __FUNCSIG__; #elif defined(__cpp_lib_source_location) return std::source_location::current().function_name(); #else return __PRETTY_FUNCTION__; #endif } static constexpr bool getValue() { // Clang: static auto QCoro::detail::is_qprivatesignal::functionName() [T = Foo] // GCC : static consteval auto QCoro::detail::is_qprivatesignal::functionName() [with T = Foo] // MSVC : const char *__cdecl QCoro::detail::is_qprivatesignal::functionName(void) // Note: can't use auto here as it gets deduced as const char* for some reason despite // functionName() explicitly returning std::string_view. constexpr std::string_view name{functionName()}; #if defined(_MSC_VER) constexpr auto end_pos = name.rfind('>'); #else constexpr auto end_pos = name.rfind(']'); #endif if ((end_pos == std::string_view::npos) || (end_pos < qprivatesignal.size())) { return false; } for (auto pos = 1U; pos <= qprivatesignal.size(); ++pos) { if (name[end_pos - pos] != qprivatesignal[qprivatesignal.size() - pos]) { return false; } } return true; } public: static constexpr bool value = getValue(); }; template constexpr bool is_qprivatesignal_v = is_qprivatesignal>::value; } // namespace QCoro::detail qcoro-0.12.0/qcoro/core/qcorocore.h000066400000000000000000000003451477357142500171560ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcoroiodevice.h" #include "qcoroprocess.h" #include "qcorosignal.h" #include "qcorotimer.h" #include "qcorofuture.h" qcoro-0.12.0/qcoro/core/qcorofuture.h000066400000000000000000000107711477357142500175440ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include "qcorotask.h" #include "macros_p.h" #include #include #include /*! \cond internal */ namespace QCoro::detail { template class QCoroFuture final { private: template class WaitForFinishedOperationBase { public: explicit WaitForFinishedOperationBase(const QFuture &future) : mFuture(future) {} Q_DISABLE_COPY(WaitForFinishedOperationBase) QCORO_DEFAULT_MOVE(WaitForFinishedOperationBase) bool await_ready() const noexcept { return mFuture.isFinished() || mFuture.isCanceled(); } void await_suspend(std::coroutine_handle<> awaitingCoroutine) { auto *watcher = new QFutureWatcher(); QObject::connect(watcher, &QFutureWatcherBase::finished, [watcher, awaitingCoroutine]() mutable { watcher->deleteLater(); awaitingCoroutine.resume(); }); watcher->setFuture(mFuture); } protected: QFuture mFuture; }; class WaitForFinishedOperationImplT : public WaitForFinishedOperationBase { public: using WaitForFinishedOperationBase::WaitForFinishedOperationBase; T await_resume() const { return this->mFuture.result(); } }; class WaitForFinishedOperationImplVoid : public WaitForFinishedOperationBase { public: using WaitForFinishedOperationBase::WaitForFinishedOperationBase; void await_resume() { // This won't block, since we know for sure that the QFuture is already finished. // The weird side-effect of this function is that it will re-throw the stored // exception. this->mFuture.waitForFinished(); } }; using WaitForFinishedOperation = std::conditional_t< std::is_void_v, WaitForFinishedOperationImplVoid, WaitForFinishedOperationImplT>; #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) template requires (!std::is_void_v) class TakeResultOperation : public WaitForFinishedOperationBase { public: using WaitForFinishedOperationBase::WaitForFinishedOperationBase; T_ await_resume() { return this->mFuture.takeResult(); } }; #endif friend struct awaiter_type>; QFuture mFuture; public: explicit QCoroFuture(const QFuture &future) : mFuture(future) {} /*! * \brief Equivalent to using `QCoroFuture::result()`. * * This function is provided for backwards API compatibility, new code should use * `QCoroFuture::result()` instead. * * \see QCoroFuture::result() */ Task waitForFinished() { co_return co_await WaitForFinishedOperation{mFuture}; } /*! * \brief Asynchronously waits for the future to finish and returns the result. * * This is equivalent to using `QFutureWatcher` to wait for the future to finish and * then obtainign the result using `QFuture::result()`. * * \see QCoroFuture::takeResult() */ Task result() { co_return co_await WaitForFinishedOperation{mFuture}; } #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) /*! * \brief Asynchronously waits for the future to finish and takes (moves) the result from the future object. * * This is useful when you want to move the result from the future object into a local variable without * copying it or when working with move-only types. * * Using this is equivalent to using `QFutureWatcher` to wait for the future to finish and then * obtaining the result using `QFuture::takeResult()`. * * \see QCoroFuture::result() */ Task takeResult() requires (!std::is_void_v) { co_return std::move(co_await TakeResultOperation{mFuture}); } #endif }; template struct awaiter_type> { using type = typename QCoroFuture::WaitForFinishedOperation; }; } // namespace QCoro::detail /*! \endcond */ //! Returns a coroutine-friendly wrapper for QFuture object. /*! * Returns a wrapper for the QFuture \c f that provides coroutine-friendly * way to co_await the completion of the future. * * @see docs/reference/qfuture.md */ template inline auto qCoro(const QFuture &f) noexcept { return QCoro::detail::QCoroFuture{f}; } qcoro-0.12.0/qcoro/core/qcoroiodevice.cpp000066400000000000000000000111361477357142500203500ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcoroiodevice.h" #include "qcoroiodevice_p.h" #include "qcorosignal.h" #include #include #include using namespace QCoro::detail; QCoroIODevice::OperationBase::OperationBase(QIODevice *device) : mDevice(device) {} void QCoroIODevice::OperationBase::finish(std::coroutine_handle<> awaitingCoroutine) { QObject::disconnect(mConn); QObject::disconnect(mCloseConn); // Delayed trigger QTimer::singleShot(0, [awaitingCoroutine]() mutable { awaitingCoroutine.resume(); }); } QCoroIODevice::ReadOperation::ReadOperation(QIODevice *device, std::function &&resultCb) : OperationBase(device), mResultCb(std::move(resultCb)) {} bool QCoroIODevice::ReadOperation::await_ready() const noexcept { return !mDevice || !mDevice->isOpen() || !mDevice->isReadable() || mDevice->bytesAvailable() > 0; } void QCoroIODevice::ReadOperation::await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept { Q_ASSERT(mDevice); mConn = QObject::connect(mDevice, &QIODevice::readyRead, std::bind(&ReadOperation::finish, this, awaitingCoroutine)); mCloseConn = QObject::connect(mDevice, &QIODevice::aboutToClose, std::bind(&ReadOperation::finish, this, awaitingCoroutine)); } QByteArray QCoroIODevice::ReadOperation::await_resume() { return mResultCb(mDevice); } QCoroIODevice::ReadAllOperation::ReadAllOperation(QIODevice *device) : ReadOperation(device, [](QIODevice *d) { return d->readAll(); }) {} QCoroIODevice::ReadAllOperation::ReadAllOperation(QIODevice &device) : ReadAllOperation(&device) {} QCoroIODevice::QCoroIODevice(QIODevice *device) : mDevice{device} {} QCoro::Task QCoroIODevice::readAll(std::chrono::milliseconds timeout) { const auto device = mDevice; if (!co_await waitForReadyRead(timeout)) { co_return QByteArray{}; } co_return device->readAll(); } QCoro::Task QCoroIODevice::read(qint64 maxSize, std::chrono::milliseconds timeout) { const auto device = mDevice; if (!co_await waitForReadyRead(timeout)) { co_return QByteArray{}; } co_return device->read(maxSize); } QCoro::Task QCoroIODevice::readLine(qint64 maxSize, std::chrono::milliseconds timeout) { const auto device = mDevice; if (!co_await waitForReadyRead(timeout)) { co_return QByteArray{}; } co_return device->readLine(maxSize); } QCoro::Task QCoroIODevice::write(const QByteArray &buffer) { const auto bytesWritten = mDevice->write(buffer); qint64 bytesConfirmed = 0; while (bytesConfirmed < bytesWritten) { const auto flushed = co_await waitForBytesWritten(-1); if (!flushed.has_value()) { // There was an intermediate error and we don't know how much was actually // written, so we report only what we know for sure was written. break; } bytesConfirmed += *flushed; } co_return bytesConfirmed; } QCoro::Task QCoroIODevice::waitForReadyRead(int timeout_msecs) { return waitForReadyRead(std::chrono::milliseconds(timeout_msecs)); } QCoro::Task QCoroIODevice::waitForReadyRead(std::chrono::milliseconds timeout) { if (!mDevice->isReadable()) { co_return false; } if (mDevice->bytesAvailable() > 0) { co_return true; } const auto result = co_await waitForReadyReadImpl(timeout); co_return result.has_value(); } QCoro::Task> QCoroIODevice::waitForBytesWritten(int timeout_msecs) { return waitForBytesWritten(std::chrono::milliseconds(timeout_msecs)); } QCoro::Task> QCoroIODevice::waitForBytesWritten(std::chrono::milliseconds timeout) { if (!mDevice->isWritable()) { co_return std::nullopt; } if (mDevice->bytesToWrite() == 0) { co_return 0; } const auto result = co_await waitForBytesWrittenImpl(timeout); co_return result; } QCoro::Task> QCoroIODevice::waitForReadyReadImpl(std::chrono::milliseconds timeout) { WaitSignalHelper helper(mDevice.data(), &QIODevice::readyRead); co_return co_await qCoro(&helper, qOverload(&WaitSignalHelper::ready), timeout); } QCoro::Task> QCoroIODevice::waitForBytesWrittenImpl(std::chrono::milliseconds timeout) { WaitSignalHelper helper(mDevice.data(), &QIODevice::bytesWritten); co_return co_await qCoro(&helper, qOverload(&WaitSignalHelper::ready), timeout); }qcoro-0.12.0/qcoro/core/qcoroiodevice.h000066400000000000000000000214631477357142500200210ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include "qcorotask.h" #include "coroutine.h" #include "macros_p.h" #include "waitoperationbase_p.h" #include "qcorocore_export.h" #include class QIODevice; /*! \cond internal */ namespace QCoro::detail { class QCOROCORE_EXPORT QCoroIODevice { private: class OperationBase { public: Q_DISABLE_COPY(OperationBase) QCORO_DEFAULT_MOVE(OperationBase) virtual ~OperationBase() = default; protected: explicit OperationBase(QIODevice *device); virtual void finish(std::coroutine_handle<> awaitingCoroutine); QPointer mDevice; QMetaObject::Connection mConn; QMetaObject::Connection mCloseConn; QMetaObject::Connection mFinishedConn; }; protected: class ReadOperation : public OperationBase { public: ReadOperation(QIODevice *device, std::function &&resultCb); Q_DISABLE_COPY(ReadOperation) QCORO_DEFAULT_MOVE(ReadOperation) virtual bool await_ready() const noexcept; virtual void await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept; QByteArray await_resume(); private: std::function mResultCb; }; class ReadAllOperation final : public ReadOperation { public: explicit ReadAllOperation(QIODevice *device); explicit ReadAllOperation(QIODevice &device); }; template friend struct awaiter_type; public: //! Constructor. explicit QCoroIODevice(QIODevice *device); /*! * \brief Co_awaitable equivalent to [`QIODevice::readAll()`][qdoc-qiodevice-readall]. * * Waits until the `QIODevice` emits [`readyRead()`][qdoc-qiodevice-readyRead] and * then calls `readAll()`. * * Optional parameter timeout specifies how long to wait for the operation to * complete. If timeout occurs before any data are available for reading, the * operation will return an empty QByteArray. If the \c timeout is -1, the operation * will never time out. * * Identical to asynchronously calling * ```cpp * device.waitForReadyRead(timeout); * device.readAll(); * ``` * * [qdoc-qiodevice-readall]: https://doc.qt.io/qt-5/qiodevice.html#readAll * [qdoc-qiodevice-readyRead]: https://doc.qt.io/qt-5/qiodevice.html#readyRead */ Task readAll(std::chrono::milliseconds timeout = std::chrono::milliseconds{-1}); /*! * \brief Co_awaitable equivalent to [`QIODevice::read()`][qdoc-qiodevice-read]. * * Waits until the `QIODevice` emits [`readyRead()`][qdoc-qiodevice-readyRead] and * then calls [`read()`][qdoc-qiodevice-read] to read up to \c maxSize bytes. * * Optional parameter timeout specifies how long to wait for the operation to * complete. If timeout occurs before any data are available for reading, the * operation will return an empty QByteArray. If the timeout is -1, the operation * will never time out. * * Identical to asynchronously calling * ```cpp * device.waitForReadyRead(timeout); * device.read(); * ``` * * [qdoc-qiodevice-read]: https://doc.qt.io/qt-5/qiodevice.html#read-1 * [qdoc-qiodevice-readyRead]: https://doc.qt.io/qt-5/qiodevice.html#readyRead */ Task read(qint64 maxSize, std::chrono::milliseconds timeout = std::chrono::milliseconds{-1}); /*! * \brief Co_awaitable equivalent to [`QIODevice::readLine()`][qdoc-qiodevice-readLine]. * * Waits until the `QIODevice` emits [`readyRead()`][qdoc-qiodevice-readyRead] and * then calls [`readLine()`][qdoc-qiodevice-readLIne] to read until the end-of-line * character is reached or up to \c maxSize characters are read. * * Optional parameter timeout specifies how long to wait for the operation to * complete. If timeout occurs before any data are available for reading, the * operation will return an empty QByteArray. If the \c timeout is -1, the operation * will never time out. * * Identical to asynchronously calling * ```cpp * device.waitForReadyRead(); * device.readLine(); * ``` * * [qdoc-qiodevice-readLine]: https://doc.qt.io/qt-5/qiodevice.html#readLine * [qdoc-qiodevice-readyRead]: https://doc.qt.io/qt-5/qiodevice.html#readyRead */ Task readLine(qint64 maxSize = 0, std::chrono::milliseconds timeout = std::chrono::milliseconds{-1}); // TODO //auto bytesAvailable(qint64 minBytes) { /*! * \brief Co_awaitable equivalent to [`QIODevice::write`][qdoc-qiodevice-write]. * * Returns immediately if the `QIODevice` is unbuffered, blocks until the `QIODevice` * emits [`bytesWritten()`][qdoc-qiodevice-bytesWritten] signal with total bytes equal * to the size of the input \c buffer. * * Identical to asynchronously calling * ```cpp * device.write(data); * device.waitForBytesWritten(); * ``` * * [qdoc-qiodevice-write]: https://doc.qt.io/qt-5/qiodevice.html#write-2 * [qdoc-qiodevice-bytesWritten]: https://doc.qt.io/qt-5/qiodevice.html#bytesWritten */ Task write(const QByteArray &buffer); /*! * \brief Co_awaitable equivalent to [`QIODevice::waitForReadyRead`][qdoc-qiodevice-waitForReadyRead]. * * Unlike the Qt version, this overload uses `std::chrono::milliseconds` to express the * timeout rather than plain `int`. If the \c timeout is -1, the operation will enver time out. * * [qdoc-qiodevice-waitForReadyRead]: https://doc.qt.io/qt-5/qiodevice.html#waitForReadyRead */ Task waitForReadyRead(std::chrono::milliseconds timeout); /*! * \brief Co_awaitable equivalent to [`QIODevice::waitForReadyRead`][qdoc-qiodevice-waitForReadyRead]. * * If the \c timeout_msecs is -1, the operation will never time out. * * [qdoc-qiodevice-waitForReadyRead]: https://doc.qt.io/qt-5/qiodevice.html#waitForReadyRead */ Task waitForReadyRead(int timeout_msecs); /*! * \brief Co_awaitable equivalent to [`QIODevice::waitForBytesWritten`][qdoc-qiodevice-waitForBytesWritten]. * * Unlike the Qt version, this overload uses `std::chrono::milliseconds` to express the * timeout rather than plain `int`. If the \c timeout is -1, the operation will never time out. * * [qdoc-qiodevice-waitForBytesWritten]: https://doc.qt.io/qt-5/qiodevice.html#waitForBytesWritten */ Task> waitForBytesWritten(std::chrono::milliseconds timeout); /*! * \brief Co_awaitable equivalent to [`QIODevice::waitForBytesWritten`][qdoc-qiodevice-waitForBytesWritten]. * * If the \c timeout_msecs is -1, the operation will never time out. * * [qdoc-qiodevice-waitForBytesWritten]: https://doc.qt.io/qt-5/qiodevice.html#waitForBytesWritten */ Task> waitForBytesWritten(int timeout_msecs); protected: virtual Task> waitForReadyReadImpl(std::chrono::milliseconds timeout); virtual Task> waitForBytesWrittenImpl(std::chrono::milliseconds timeout); QPointer mDevice = {}; }; template requires std::is_base_of_v struct awaiter_type { using type = QCoroIODevice::ReadAllOperation; }; template requires std::is_base_of_v struct awaiter_type { using type = QCoroIODevice::ReadAllOperation; }; } // namespace QCoro::detail /*! \endcond */ //! Returns a coroutine-friendly wrapper for a QIODevice-derived object. /*! * Returns a wrapper for QIODevice \c d that provides coroutine-friendly way * of co_awaiting reading and writing operation. * * @see docs/reference/qiodevice.md */ inline auto qCoro(QIODevice &d) noexcept { return QCoro::detail::QCoroIODevice{&d}; } //! \copydoc qCoro(QIODevice *d) noexcept inline auto qCoro(QIODevice *d) noexcept { return QCoro::detail::QCoroIODevice{d}; } // If you got here due to a compile error, make sure to #include the QCoro header // for the corresponding class, so that the qCoro() overload that wraps the QIODevice-derived // classes in their respective QCoroIODevice-derived wrappers is used. // // Wrapping those classes directly into QCoroIODevice will cause co_awaiting certain operations to not // work as expected. class QAbstractSocket; auto qCoro(QAbstractSocket *) noexcept; // You are likely missing "#include " class QLocalSocket; auto qCoro(QLocalSocket *) noexcept; // You are likely missing "#include " class QNetworkReply; auto qCoro(QNetworkReply *) noexcept; // You are likely missing "#include " qcoro-0.12.0/qcoro/core/qcoroiodevice_p.cpp000066400000000000000000000014321477357142500206650ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcoroiodevice_p.h" using namespace QCoro::detail; WaitSignalHelper::WaitSignalHelper(const QIODevice *device, void(QIODevice::*signalFunc)()) : QObject() , mReady(connect(device, signalFunc, this, [this]() { this->emitReady(true); })) , mAboutToClose(connect(device, &QIODevice::aboutToClose, this, [this]() { this->emitReady(false); })) {} WaitSignalHelper::WaitSignalHelper(const QIODevice *device, void(QIODevice::*signalFunc)(qint64)) : QObject() , mReady(connect(device, signalFunc, this, &WaitSignalHelper::emitReady)) , mAboutToClose(connect(device, &QIODevice::aboutToClose, this, [this]() { this->emitReady(static_cast(0)); })) {} qcoro-0.12.0/qcoro/core/qcoroiodevice_p.h000066400000000000000000000015441477357142500203360ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include #include "qcorocore_export.h" namespace QCoro::detail { class QCOROCORE_EXPORT WaitSignalHelper : public QObject { Q_OBJECT public: explicit WaitSignalHelper(const QIODevice *device, void(QIODevice::*signalFunc)()); explicit WaitSignalHelper(const QIODevice *device, void(QIODevice::*signalFunc)(qint64)); Q_SIGNALS: void ready(bool result); void ready(qint64 result); protected: template void emitReady(T result) { cleanup(); Q_EMIT this->ready(result); } virtual void cleanup() { disconnect(mReady); disconnect(mAboutToClose); } QMetaObject::Connection mReady; QMetaObject::Connection mAboutToClose; }; } // namespace QCoro::detailqcoro-0.12.0/qcoro/core/qcoroprocess.cpp000066400000000000000000000036131477357142500202400ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include #if QT_CONFIG(process) #include "qcoroprocess.h" #include "qcorosignal.h" #include using namespace QCoro::detail; QCoroProcess::QCoroProcess(QProcess *process) : QCoroIODevice(process) {} QCoro::Task QCoroProcess::waitForStarted(int timeout_msecs) { return waitForStarted(std::chrono::milliseconds{timeout_msecs}); } QCoro::Task QCoroProcess::waitForStarted(std::chrono::milliseconds timeout) { const auto *process = qobject_cast(mDevice.data()); if (process->state() == QProcess::Starting) { const auto started = co_await qCoro(process, &QProcess::started, timeout); co_return started.has_value(); } co_return process->state() == QProcess::Running; } QCoro::Task QCoroProcess::waitForFinished(int timeout_msecs) { return waitForFinished(std::chrono::milliseconds{timeout_msecs}); } QCoro::Task QCoroProcess::waitForFinished(std::chrono::milliseconds timeout) { const auto *process = qobject_cast(mDevice.data()); if (process->state() == QProcess::NotRunning) { co_return false; } const auto finished = co_await qCoro(process, qOverload(&QProcess::finished), timeout); co_return finished.has_value(); } QCoro::Task QCoroProcess::start(QIODevice::OpenMode mode, std::chrono::milliseconds timeout) { static_cast(mDevice.data())->start(mode); return waitForStarted(timeout); } QCoro::Task QCoroProcess::start(const QString &program, const QStringList &arguments, QIODevice::OpenMode mode, std::chrono::milliseconds timeout) { static_cast(mDevice.data())->start(program, arguments, mode); return waitForStarted(timeout); } #endif // QT_CONFIG(process) qcoro-0.12.0/qcoro/core/qcoroprocess.h000066400000000000000000000117341477357142500177100ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include "waitoperationbase_p.h" #include "qcoroiodevice.h" #include "qcorocore_export.h" #include #include #if QT_CONFIG(process) class QProcess; namespace QCoro::detail { using namespace std::chrono_literals; //! QProcess wrapper with co_awaitable-friendly API. class QCOROCORE_EXPORT QCoroProcess : public QCoroIODevice { public: explicit QCoroProcess(QProcess *process); /*! * \brief Co_awaitable equivalent to [`QProcess::waitForStarted()`][qtdoc-qprocess-waitForStarted]. * * Returns true if the process has started successfully, otherwise returns false (if the * operation timed out or if an error occured). * * If \c timeout_msecs is -1 the operation will never time out. * * [qtdoc-qprocess-waitForStarted]: https://doc.qt.io/qt-5/qprocess.html#waitForStarted */ Task waitForStarted(int timeout_msecs = 30'000); /*! * \brief Co_awaitable equivalent to [`QProcess::waitForStarted()`][qtdoc-qprocess-waitForStarted]. * * Returns true if the process has started successfully, otherwise returns false (if the * operation timed out or if an error occured). * * Unlike the Qt version, this overload uses `std::chrono::milliseconds` to express the * timeout rather than plain `int`. If the \c timeout is -1 the operation will never time out. * * [qtdoc-qprocess-waitForStarted]: https://doc.qt.io/qt-5/qprocess.html#waitForStarted */ Task waitForStarted(std::chrono::milliseconds timeout); /*! * \brief Co_awaitable equivalent to [`QProcess::waitForFinished()`][qtdoc-qprocess-waitForFinished]. * * Returns true if the process has finished, otherwise returns false (if the operation timed * out, if an error occured or if this `QProcess` is already finished. * * If \c timeout_msecs is -1 the operation will never time out. * * [qtdoc-qprocess-waitForFinished]: https://doc.qt.io/qt-5/qprocess.html#waitForFinished */ Task waitForFinished(int timeout_msecs = 30'000); /*! * \brief Co_awaitable equivalent to [`QProcess::waitForFinished()`][qtdoc-qprocess-waitForFinished]. * * Returns true if the process has finished, otherwise returns false (if the operation timed * out, if an error occured or if this `QProcess` is already finished. * * Unlike the Qt version, this overload uses `std::chrono::milliseconds` to express the * timeout rather than plain `int`. If the \c timeout is -1 the operation will never time out. * * [qtdoc-qprocess-waitForFinished]: https://doc.qt.io/qt-4/qprocess.html#waitForFinished */ Task waitForFinished(std::chrono::milliseconds timeout); /*! * \brief Executes a new process and waits for it to start * * Co_awaitable equivalent to calling [`QProcess::start()`][qtdoc-qprocess-start-2] * followed by [`QProcess::waitForStarted()`][qtdoc-qprocess-waitForStarted]. * * Returns true if the process has started successfully, otherwise returns false (if the * operation timed out or if an error occurred). * * If the \c timeout is -1 the operation will never time out. * * [qtdoc-qprocess-waitForStarted]: https://doc.qt.io/qt-5/qprocess.html#waitForStarted * [qtdoc-qprocess-start-2]: https://doc.qt.io/qt-5/qprocess.html#start-2 */ Task start(QIODevice::OpenMode mode = QIODevice::ReadWrite, std::chrono::milliseconds timeout = std::chrono::seconds(30)); /*! * \brief Executes a new process and waits for it to start * * Co_awaitable equivalent to calling [`QProcess::start()`][qtdoc-qprocess-start] * followed by [`QProcess::waitForStarted()`][qtdoc-qprocess-waitForStarted]. * * Returns true if the process has started successfully, otherwise returns false (if the * operation timed out or if an error occurred). * * If the \c timeout is -1 the operation will never time out. * * [qtdoc-qprocess-waitForStarted]: https://doc.qt.io/qt-5/qprocess.html#waitForStarted * [qtdoc-qprocess-start]: https://doc.qt.io/qt-5/qprocess.html#start-2 */ Task start(const QString &program, const QStringList &arguments, QIODevice::OpenMode mode = QIODevice::ReadWrite, std::chrono::milliseconds timeout = std::chrono::seconds(30)); }; } // namespace QCoro::detail //! Returns a coroutine-friendly wrapper for QProcess object. /*! * Returns a wrapper for the QProcess \c p that provides coroutine-friendly * way to co_await the process to start or finish. * * @see docs/reference/qprocess.md */ inline auto qCoro(QProcess &p) noexcept { return QCoro::detail::QCoroProcess{&p}; } //! \copydoc qCoro(QProcess &p) noexcept inline auto qCoro(QProcess *p) noexcept { return QCoro::detail::QCoroProcess{p}; } #endif // QT_CONFIG(process) qcoro-0.12.0/qcoro/core/qcorosignal.h000066400000000000000000000324131477357142500175040ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021-2023 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include "coroutine.h" #include "macros_p.h" #include "concepts_p.h" #include "qcorotask.h" #include "qcoroasyncgenerator.h" #include #include #include #include #include #include #include #include #include "impl/isqprivatesignal.h" namespace QCoro::detail { namespace concepts { //! Simplistic QObject concept. template concept QObject = requires(T *obj) { requires std::is_base_of_v<::QObject, T>; requires std::is_same_v; }; } // namespace concepts template class QCoroSignalBase { private: template struct concat_tuple; template struct concat_tuple> { using type = std::tuple; }; template struct filtered_tuple; template struct filtered_tuple { using type = std::conditional_t< is_qprivatesignal_v, typename filtered_tuple::type, typename filtered_tuple::type, Args...>::type>; }; template struct filtered_tuple { using type = Tuple; }; template struct args_tuple; template struct args_tuple { using type = std::tuple...>; }; template struct args_tuple { using type = typename filtered_tuple, std::remove_cvref_t...>::type; }; template struct result_type_from_tuple; template struct result_type_from_tuple> { using type = Arg; }; template struct result_type_from_tuple> { using type = std::tuple; }; using result_tuple = typename args_tuple>::type; public: /**! * The result_type is std::optional of * * T if result_tuple is std::tuple * * result_tuple otherwise **/ using result_type = std::optional::type>; QCoroSignalBase(const QCoroSignalBase &) = delete; QCoroSignalBase &operator=(const QCoroSignalBase &) = delete; QCoroSignalBase(QCoroSignalBase &&) noexcept = default; QCoroSignalBase &operator=(QCoroSignalBase &&) noexcept = default; ~QCoroSignalBase() { if (static_cast(mConn)) { QObject::disconnect(mConn); } } void handleTimeout(std::coroutine_handle<> awaitingCoroutine) { if (mTimeoutTimer) { QObject::connect(mTimeoutTimer.get(), &QTimer::timeout, mObj, [this, awaitingCoroutine]() { QObject::disconnect(mConn); awaitingCoroutine.resume(); }, Qt::DirectConnection); // force coro to be resumed on our thread, not mObj's thread mTimeoutTimer->start(); } } protected: QCoroSignalBase(T *obj, FuncPtr &&funcPtr, std::chrono::milliseconds timeout) : mObj(obj), mFuncPtr(std::forward(funcPtr)) { if (timeout.count() > -1) { mTimeoutTimer = std::make_unique(); mTimeoutTimer->setInterval(timeout); mTimeoutTimer->setSingleShot(true); } } template struct select_last { using type = typename decltype((std::type_identity{}, ...))::type; }; template constexpr void storeResult(StoreResultCb &&storeResult, Args &&...args) { using LastArg = typename select_last::type; if constexpr (is_qprivatesignal_v) { // Based on https://stackoverflow.com/a/77026174/4601437 // Remove the last element (which is a QPrivateSignal) from the tuple auto all = std::forward_as_tuple(std::forward(args)...); auto reduced = [&](std::index_sequence) constexpr { return std::make_tuple(std::get(all)...); }(std::make_index_sequence{}); // Use the shortened tuple as arguments to mResult.emplace() std::apply(std::forward(storeResult), std::move(reduced)); } else { std::invoke(std::forward(storeResult), std::forward(args)...); } } template constexpr void storeResult(StoreResultCb &&storeResult) { std::invoke(std::forward(storeResult)); } protected: QPointer mObj; FuncPtr mFuncPtr; QMetaObject::Connection mConn; std::unique_ptr mTimeoutTimer; }; template class QCoroSignal : public QCoroSignalBase { public: using typename QCoroSignalBase::result_type; QCoroSignal(T *obj, FuncPtr &&ptr, std::chrono::milliseconds timeout) : QCoroSignalBase(obj, std::forward(ptr), timeout) , mDummyReceiver(std::make_unique()) {} QCoroSignal(const QCoroSignal &) = delete; QCoroSignal(QCoroSignal &&other) noexcept : QCoroSignalBase(std::move(other)) , mResult(std::move(other.mResult)) , mDummyReceiver(std::move(other.mDummyReceiver)) { if (this->mConn) { QObject::disconnect(this->mConn); setupConnection(); } } QCoroSignal &operator=(QCoroSignal &&other) noexcept { QCoroSignalBase::operator=(std::move(other)); std::swap(mResult, other.mResult); std::swap(mDummyReceiver, other.mDummyReceiver); if (this->mConn) { QObject::disconnect(this->mConn); setupConnection(); } return *this; } QCoroSignal &operator=(const QCoroSignal &) = delete; ~QCoroSignal() = default; bool await_ready() const noexcept { return this->mObj.isNull(); } void await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept { this->handleTimeout(awaitingCoroutine); mAwaitingCoroutine = awaitingCoroutine; setupConnection(); } result_type await_resume() { return std::move(mResult); } private: void setupConnection() { Q_ASSERT(!this->mConn); this->mConn = QObject::connect( this->mObj, this->mFuncPtr, this->mDummyReceiver.get(), [this](auto &&...args) mutable { if (this->mTimeoutTimer) { this->mTimeoutTimer->stop(); } QObject::disconnect(this->mConn); this->storeResult([this](auto && ...args) { mResult.emplace(std::forward(args)...); }, std::forward(args)...); if (mAwaitingCoroutine) { mAwaitingCoroutine.resume(); } }, Qt::QueuedConnection); } result_type mResult; std::coroutine_handle<> mAwaitingCoroutine; std::unique_ptr mDummyReceiver; }; template QCoroSignal(T *, FuncPtr &&, std::chrono::milliseconds) -> QCoroSignal; template class QCoroSignalQueue : public QCoroSignalBase { public: using typename QCoroSignalBase::result_type; QCoroSignalQueue(T *obj, FuncPtr &&ptr, std::chrono::milliseconds timeout) : QCoroSignalBase(obj, std::forward(ptr), timeout) { setupConnection(); } QCoroSignalQueue(QCoroSignalQueue &&) = delete; QCoroSignalQueue(const QCoroSignalQueue &) = delete; QCoroSignalQueue &operator=(QCoroSignalQueue &&) = delete; QCoroSignalQueue &operator=(const QCoroSignalQueue &) = delete; ~QCoroSignalQueue() = default; auto operator co_await() noexcept { struct Awaiter { explicit Awaiter(QCoroSignalQueue &queue) : mQueue(queue) {} bool await_ready() const noexcept { return !mQueue.isValid() || !mQueue.empty(); } void await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept { mQueue.handleTimeout(awaitingCoroutine); mQueue.setAwaiter(awaitingCoroutine); } result_type await_resume() { return mQueue.dequeue(); } private: QCoro::detail::QCoroSignalQueue &mQueue; }; return Awaiter{*this}; } bool isValid() const { return !this->mObj.isNull(); } bool empty() const { return mQueue.empty(); } result_type dequeue() { if (mQueue.empty()) { return std::nullopt; } auto result = std::move(mQueue.front()); mQueue.pop_front(); return result; } void setAwaiter(std::coroutine_handle<> awaiter) { mAwaitingCoroutine = awaiter; } private: void setupConnection() { if (this->mConn) { return; } this->mConn = QObject::connect( this->mObj, this->mFuncPtr, &this->mDummyReceiver, [this](auto && ...args) mutable { if (this->mTimeoutTimer) { this->mTimeoutTimer->stop(); } this->storeResult([this](auto && ...args) { mQueue.emplace_back(std::forward(args)...); }, std::forward(args) ...); if (mAwaitingCoroutine) { mAwaitingCoroutine.resume(); } }, Qt::QueuedConnection); } std::coroutine_handle<> mAwaitingCoroutine; std::deque mQueue; QObject mDummyReceiver; }; template QCoroSignalQueue(T *, FuncPtr &&, std::chrono::milliseconds) -> QCoroSignalQueue; } // namespace QCoro::detail //! Allows co_awaiting on signal emission with a timeout. /*! * Returns an Awaitable object that allows co_awaiting for a singal * to be emitted within the specified timeout. If the signal has exactly * one argument, then the value of the argument is returned as a result * of awaiting the coroutine. If the signal has two or more arguments, * then the arguments are returned as a tuple. If the signal has no * arguments, then the result of the coroutine is an empty tuple. * * If the timeout occurs before the signal is emitted, the result of the * coroutine is an empty optional. If the \c timeout is -1 the operation * will never time out. */ template inline auto qCoro(T *obj, FuncPtr &&ptr, std::chrono::milliseconds timeout) -> QCoro::Task::result_type> { auto result = co_await QCoro::detail::QCoroSignal(obj, std::forward(ptr), timeout); co_return std::move(result); } //! Allows co_awaiting on signal emission. /*! * Returns an Awaitable object that allows co_awaiting for a signal to * be emitted. If the signal has exactly one argument, then the value * of the argument is returned as a result of awaiting the coroutine. * If the signal has two or more arguments, then the arguments are * returned as a tuple. If the signal has no arguments, then the result * of the coroutine is an empty tuple. * * @see docs/reference/coro.md */ template inline auto qCoro(T *obj, FuncPtr &&ptr) -> QCoro::Task::result_type::value_type> { auto result = co_await qCoro(obj, std::forward(ptr), std::chrono::milliseconds{-1}); co_return std::move(*result); } template inline auto qCoroSignalListener(T *obj, FuncPtr &&ptr, std::chrono::milliseconds timeout = std::chrono::milliseconds{-1}) -> QCoro::AsyncGenerator::result_type::value_type> { using SignalQueue = QCoro::detail::QCoroSignalQueue; // The actual generator is in a wrapper function, so that we can perform // some initialization (constructing signalQueue) in the qCoroSignalListener() // function before the generator gets initially suspended. constexpr auto innerGenerator = [](std::unique_ptr signalQueue) -> QCoro::AsyncGenerator { Q_FOREVER { auto result = co_await *signalQueue; if (!result.has_value()) { // timeout break; } co_yield std::move(*result); } }; return innerGenerator(std::make_unique(obj, std::forward(ptr), timeout)); } qcoro-0.12.0/qcoro/core/qcorothread.cpp000066400000000000000000000054141477357142500200320ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcorothread.h" #include "qcorosignal.h" #include #include #include using namespace QCoro; using namespace QCoro::detail; namespace QCoro::detail { class ContextHelper : public QObject { Q_OBJECT public: static QEvent::Type eventType; explicit ContextHelper(std::coroutine_handle<> awaiter, QThread *thread) : mThread(thread) , mAwaiter(awaiter) {} bool event(QEvent *event) override { if (event->type() == eventType) { Q_ASSERT(QThread::currentThread() == mThread); mAwaiter.resume(); return true; } return QObject::event(event); } private: QThread *mThread; std::coroutine_handle<> mAwaiter; }; QEvent::Type ContextHelper::eventType = static_cast(QEvent::registerEventType()); class ThreadContextPrivate { public: explicit ThreadContextPrivate(QThread *thread) : mThread(thread) {} QThread *mThread; std::unique_ptr mContext; }; } // namespace QCoro::detail ThreadContext::ThreadContext(QThread *thread) : d(std::make_unique(thread)) {} #ifdef Q_CC_GNU ThreadContext::ThreadContext(ThreadContext &&) noexcept = default; #endif ThreadContext::~ThreadContext() = default; bool ThreadContext::await_ready() const noexcept { return false; // never ready! } void ThreadContext::await_suspend(std::coroutine_handle<> awaiter) noexcept { d->mContext = std::make_unique(awaiter, d->mThread); d->mContext->moveToThread(d->mThread); qCoro(d->mThread).waitForStarted().then([this]() { auto *event = new QEvent(static_cast(detail::ContextHelper::eventType)); QCoreApplication::postEvent(d->mContext.get(), event); }); } void ThreadContext::await_resume() noexcept {} ThreadContext QCoro::moveToThread(QThread *thread) { return ThreadContext(thread); } QCoroThread::QCoroThread(QThread *thread) : mThread(thread) {} QCoro::Task QCoroThread::waitForStarted(std::chrono::milliseconds timeout) { if (mThread->isRunning()) { co_return true; } if (mThread->isFinished()) { co_return false; } const auto result = co_await qCoro(mThread.data(), &QThread::started, timeout); co_return result.has_value(); } QCoro::Task QCoroThread::waitForFinished(std::chrono::milliseconds timeout) { if (mThread->isFinished()) { co_return true; } if (!mThread->isRunning()) { co_return false; } const auto result = co_await qCoro(mThread.data(), &QThread::finished, timeout); co_return result.has_value(); } #include "qcorothread.moc" qcoro-0.12.0/qcoro/core/qcorothread.h000066400000000000000000000054441477357142500175020ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include #include "qcorocore_export.h" #include "qcoro/coroutine.h" #include class QThread; namespace QCoro { template class Task; namespace detail { class ThreadContextPrivate; } // namespace detail class ThreadContext { public: explicit ThreadContext(QThread *thread); ~ThreadContext(); ThreadContext(const ThreadContext &) = delete; ThreadContext &operator=(const ThreadContext &) = delete; #ifdef Q_CC_GNU // Workaround for the a GCC bug(?) where GCC tries to move the ThreadContext // into QCoro::TaskPromise::await_transform() (most likely). ThreadContext(ThreadContext &&) noexcept; #else ThreadContext(ThreadContext &&) = delete; #endif ThreadContext &operator=(ThreadContext &&) = delete; bool await_ready() const noexcept; void await_suspend(std::coroutine_handle<> awaiter) noexcept; void await_resume() noexcept; private: std::unique_ptr d; }; ThreadContext moveToThread(QThread *thread); } // namespace QCoro namespace QCoro::detail { class QCOROCORE_EXPORT QCoroThread { public: explicit QCoroThread(QThread *thread); /** * \brief Coroutine that waits for a thread to get started. * * \return Returns `true` when the thread is already running or when it starts within the * specified timeout. If the thread has already finished, or the operation times out, the * coroutine returns `false`. * * If the timeout is -1 the operation will never time out. * * See [`QThread::started()`][qtdoc-qthread-started] documentation for details. * * [qtdoc-qthread-started]: https://doc.qt.io/qt-5/qthread.html#started */ Task waitForStarted(std::chrono::milliseconds timeout = std::chrono::milliseconds{-1}); /** * \brief Coroutine that waits for a thread to finish. * * \return Returns `true` when the thread has already finished or when it finishes within * specified timeout. If the thread is not running and hasn't finished yet, or when the * operation times out, the coroutine returns `false`. * * If the timeout is -1 the operation will never time out. * * See [`QThread::finished()`][qtdoc-qthread-finished] documentation for details. * * [qdoc-qthread-finished]: https://doc.qt.io/qt-5/qthread.html#finished */ Task waitForFinished(std::chrono::milliseconds timeout = std::chrono::milliseconds{-1}); private: QPointer mThread; }; } // namespace QCoro::detail inline auto qCoro(QThread *thread) { return QCoro::detail::QCoroThread(thread); } inline auto qCoro(QThread &thread) { return QCoro::detail::QCoroThread(&thread); } qcoro-0.12.0/qcoro/core/qcorotimer.cpp000066400000000000000000000023141477357142500176770ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcorotimer.h" #include "qcorosignal.h" #include #include using namespace QCoro::detail; QCoroTimer::WaitForTimeoutOperation::WaitForTimeoutOperation(QTimer *timer) : mTimer(timer) {} QCoroTimer::WaitForTimeoutOperation::WaitForTimeoutOperation(QTimer &timer) : WaitForTimeoutOperation(&timer) {} bool QCoroTimer::WaitForTimeoutOperation::await_ready() const noexcept { return !mTimer || !mTimer->isActive(); } void QCoroTimer::WaitForTimeoutOperation::await_suspend(std::coroutine_handle<> awaitingCoroutine) { if (mTimer && mTimer->isActive()) { mConn = QObject::connect(mTimer, &QTimer::timeout, [this, awaitingCoroutine]() mutable { QObject::disconnect(mConn); awaitingCoroutine.resume(); }); } else { awaitingCoroutine.resume(); } } void QCoroTimer::WaitForTimeoutOperation::await_resume() const {} QCoroTimer::QCoroTimer(QTimer *timer) : mTimer(timer) {} QCoro::Task<> QCoroTimer::waitForTimeout() const { if (mTimer->isActive()) { co_await qCoro(mTimer.data(), &QTimer::timeout); } } qcoro-0.12.0/qcoro/core/qcorotimer.h000066400000000000000000000043531477357142500173510ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include "qcorotask.h" #include "qcorocore_export.h" #include #include #include /*! \cond internal */ namespace QCoro::detail { class QCOROCORE_EXPORT QCoroTimer { private: class WaitForTimeoutOperation { public: explicit WaitForTimeoutOperation(QTimer *timer); explicit WaitForTimeoutOperation(QTimer &timer); bool await_ready() const noexcept; void await_suspend(std::coroutine_handle<> awaitingCoroutine); void await_resume() const; private: QMetaObject::Connection mConn; QPointer mTimer; }; friend struct awaiter_type; friend struct awaiter_type; QPointer mTimer; public: explicit QCoroTimer(QTimer *timer); Task waitForTimeout() const; }; template<> struct awaiter_type { using type = QCoroTimer::WaitForTimeoutOperation; }; template<> struct awaiter_type { using type = QCoroTimer::WaitForTimeoutOperation; }; } // namespace QCoro::detail namespace QCoro { //! A coroutine that suspends for given period of time. template QCoro::Task<> sleepFor(const std::chrono::duration &timeout) { QTimer timer; timer.setSingleShot(true); timer.start(std::chrono::duration_cast(timeout)); co_await timer; } //! A coroutine that suspends until the specified time. template QCoro::Task<> sleepUntil(const std::chrono::time_point &when) { const auto tp = when.time_since_epoch() - std::chrono::steady_clock::now().time_since_epoch(); return sleepFor(tp); } } // namespace QCoro /*! \endcond */ //! Returns a coroutine-friendly wrapper for QTimer object. /*! * Returns a wrapper for the QTimer \c timer that provides coroutine-friendly * way to co_await the timeout. * * @see docs/reference/qtimer.md */ inline auto qCoro(QTimer *timer) noexcept { return QCoro::detail::QCoroTimer{timer}; } //! \copydoc qCoro(QTimer *) inline auto qCoro(QTimer &timer) noexcept{ return QCoro::detail::QCoroTimer{&timer}; } qcoro-0.12.0/qcoro/coroutine.h000066400000000000000000000215631477357142500162460ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include #include // __cpp_lib_coroutine is not defined if the compiler doesn't support coroutines // (__cpp_impl_coroutine), e.g. clang as of 13.0. #if defined(__cpp_lib_coroutine) #include #elif defined(__clang__) // Implement our own header in a way that is compatible with the standard. // See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/n4849.pdf #include // void_t #include // size_t // Intrinsincs for Clang // https://clang.llvm.org/docs/LanguageExtensions.html#c-coroutines-support-builtins extern "C" { void __builtin_coro_destroy(void *addr); void __builtin_coro_resume(void *addr); bool __builtin_coro_done(void *addr); void* __builtin_coro_promise(void *addr, int alignment, bool from_promise); void *__builtin_coro_noop(); } // 17.12.1 Header synopsis namespace std { // 17.12.2, coroutine traits // (omitted, because we implement them in std::experimental namespace and import them into the std // namespace). // template // struct coroutine_traits; // 17.12.3, coroutine traits template struct coroutine_handle; // 17.12.3.6, comparison operators constexpr bool operator==(coroutine_handle<> x, coroutine_handle<> y) noexcept; // constexpr strong_ordering operator<=>(coroutine_handle<> x, coroutine_handle<> y) noexcept; // 17.12.3.7, hash support //template struct hash; //template struct hash>; // 17.12.4, n-op- coroutines struct noop_coroutine_promise; template<> struct coroutine_handle; using noop_coroutine_handle = coroutine_handle; noop_coroutine_handle noop_coroutine() noexcept; // 17.12.5, trivial awaitables struct suspend_never; struct suspend_always; } // namespace std // Implementation namespace std { // Clang checks for std::experimental::coroutine_traits explicitly, so we must define the types // in the experimental namespace. namespace experimental { template struct __coroutine_traits_base {}; template struct __coroutine_traits_base> { using promise_type = typename R::promise_type; }; // 17.12.2, coroutine traits template struct coroutine_traits : __coroutine_traits_base {}; // Clang requires that std::experimental::coroutine_handle is a class template template struct coroutine_handle : public std::coroutine_handle {}; } // namespace experimental #if defined(__cpp_lib_coroutine) // Import std::experimental::coroutine_traits into the std namespace template using coroutine_traits = std::experimental::coroutine_traits; #endif // 17.12.3, coroutine handle template<> struct coroutine_handle { // 17.12.3.1, construct/reset constexpr coroutine_handle() noexcept {} constexpr coroutine_handle(nullptr_t) noexcept {} coroutine_handle &operator=(nullptr_t) noexcept { m_ptr = nullptr; return *this; } // 17.12.3.2, export/import constexpr void *address() const noexcept { return m_ptr; } static constexpr coroutine_handle from_address(void *addr) noexcept { coroutine_handle handle; handle.m_ptr = addr; return handle; } // 17.12.3.3, observers constexpr explicit operator bool() const noexcept { return m_ptr != nullptr; } bool done() const { return __builtin_coro_done(m_ptr); } // 17.12.3.4, resumption void operator()() const { resume(); } void resume() const { __builtin_coro_resume(m_ptr); } void destroy() const { __builtin_coro_destroy(m_ptr); } protected: void *m_ptr = nullptr; }; template struct coroutine_handle : public coroutine_handle<> { // 17.12.3.1, construct, reset using coroutine_handle<>::coroutine_handle; static coroutine_handle from_promise(Promise &promise) { coroutine_handle handle; handle.m_ptr = __builtin_coro_promise(&promise, alignof(Promise), /* from-promise=*/ true); return handle; } coroutine_handle &operator=(nullptr_t) noexcept { this->m_ptr = nullptr; return *this; } // 17.12.3.2, export/import static constexpr coroutine_handle from_address(void *addr) noexcept { coroutine_handle handle; handle.m_ptr = addr; return handle; } //17.12.3.5, promise access Promise &promise() const { return *reinterpret_cast( __builtin_coro_promise(m_ptr, alignof(Promise), /*from-promise=*/false)); } }; // 17.12.3.6, comparison operators constexpr bool operator==(coroutine_handle<> x, coroutine_handle<> y) noexcept { return x.address() == y.address(); } //constexpr strong_ordering operator<=>(coroutine_handle<> x, coroutine_handle<> y) noexcept; // 17.12.4, no-op coroutines struct noop_coroutine_promise {}; template<> struct coroutine_handle; using noop_coroutine_handle = coroutine_handle; template<> struct coroutine_handle : public coroutine_handle<> { // 17.12.4.2.1, observers constexpr explicit operator bool() const noexcept { return true; } constexpr bool done() const noexcept { return false; } constexpr void operator()() const noexcept {} constexpr void resume() const noexcept {} constexpr void destroy() const noexcept {} noop_coroutine_promise &promise() const noexcept { return *reinterpret_cast( __builtin_coro_promise(__builtin_coro_noop(), alignof(noop_coroutine_promise), false)); } private: coroutine_handle() noexcept : coroutine_handle<>(from_address(__builtin_coro_noop())) {} friend noop_coroutine_handle noop_coroutine() noexcept; }; inline noop_coroutine_handle noop_coroutine() noexcept { return {}; } // 17.12.5, trivial awaitables struct suspend_never { constexpr bool await_ready() const noexcept { return true; } constexpr void await_resume() const noexcept {} constexpr void await_suspend(coroutine_handle<>) const noexcept {} }; struct suspend_always { constexpr bool await_ready() const noexcept { return false; } constexpr void await_suspend(coroutine_handle<>) const noexcept {} constexpr void await_resume() const noexcept {} }; } // namespace std #else // defined(__clang__) #pragma error "Current compiler does not support coroutines, or is not supported by QCoro." #endif // defined(__cpp_lib_coroutine) // The QCORO_STD macro is no longer needed (with the code above), but keep it for backwards // compatibility. #ifdef QCORO_NO_DEPRECATED_QCOROSTD #define QCORO_STD std #else // QCORO_NO_DEPRECATED_QCOROSTD #ifdef _MSC_VER #define _QCORO_STRINGIFY2(x) #x #define _QCORO_STRINGIFY(x) _QCORO_STRINGIFY2(x) #define QCORO_STD \ __pragma(message(__FILE__ "(" _QCORO_STRINGIFY(__LINE__) ") QCORO_STD macro is deprecated, use regular 'std' namespace instead, or pass /DQCORO_NO_DEPRECATED_QCOROSTD to suppress this warning.")) \ std #else // GCC, clang #define QCORO_STD \ _Pragma("GCC warning \"QCORO_STD macro is deprecated, use regular 'std' namespace instead, or pass -DQCORO_NO_DEPRECATED_QCOROSTD to suppress this warning.\"") \ std #endif // _MSC_VER #endif // QCORO_NO_DEPRECATED_QCOROSTD // Moc doesn't seem to understand something in the header... #ifndef Q_MOC_RUN #include "concepts_p.h" namespace QCoro { namespace detail { template concept has_await_methods = requires(T t) { { t.await_ready() } -> std::same_as; {t.await_suspend(std::declval>())}; {t.await_resume()}; }; template concept has_member_operator_coawait = requires(T t) { // TODO: Check that result of co_await() satisfies Awaitable again { t.operator co_await() }; }; template concept has_nonmember_operator_coawait = requires(T t) { // TODO: Check that result of the operator satisfied Awaitable again #if defined(_MSC_VER) && !defined(__clang__) // FIXME: MSVC is unable to perform ADL lookup for operator co_await and just fails to compile { ::operator co_await(static_cast(t)) }; #else { operator co_await(static_cast(t)) }; #endif }; } // namespace detail //! A concept describing the Awaitable type /*! * Awaitable type is a type that can be passed as an argument to co_await. */ template concept Awaitable = detail::has_member_operator_coawait || detail::has_nonmember_operator_coawait || detail::has_await_methods; } // namespace QCoro #endif // Q_MOC_RUN qcoro-0.12.0/qcoro/dbus/000077500000000000000000000000001477357142500150145ustar00rootroot00000000000000qcoro-0.12.0/qcoro/dbus/CMakeLists.txt000066400000000000000000000005671477357142500175640ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2021 Daniel Vrátil # # SPDX-License-Identifier: MIT add_qcoro_library( NAME DBus SOURCES qcorodbuspendingcall.cpp CAMELCASE_HEADERS QCoroDBus QCoroDBusPendingCall QCoroDBusPendingReply QCORO_LINK_LIBRARIES PUBLIC Coro Core QT_LINK_LIBRARIES PUBLIC Core DBus ) qcoro-0.12.0/qcoro/dbus/qcorodbus.h000066400000000000000000000002531477357142500171660ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcorodbuspendingcall.h" #include "qcorodbuspendingreply.h" qcoro-0.12.0/qcoro/dbus/qcorodbuspendingcall.cpp000066400000000000000000000025121477357142500217220ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcorodbuspendingcall.h" #include "qcorosignal.h" #include using namespace QCoro::detail; QCoroDBusPendingCall::WaitForFinishedOperation::WaitForFinishedOperation(const QDBusPendingCall &call) : mCall(call) {} bool QCoroDBusPendingCall::WaitForFinishedOperation::await_ready() const noexcept { return mCall.isFinished(); } void QCoroDBusPendingCall::WaitForFinishedOperation::await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept { auto *watcher = new QDBusPendingCallWatcher{mCall}; QObject::connect(watcher, &QDBusPendingCallWatcher::finished, [awaitingCoroutine](auto *watcher) mutable { awaitingCoroutine.resume(); watcher->deleteLater(); }); } QDBusMessage QCoroDBusPendingCall::WaitForFinishedOperation::await_resume() const { Q_ASSERT(mCall.isFinished()); return mCall.reply(); } QCoroDBusPendingCall::QCoroDBusPendingCall(const QDBusPendingCall &call) : mCall(call) {} QCoro::Task QCoroDBusPendingCall::waitForFinished() { QDBusPendingCallWatcher watcher{mCall}; co_await qCoro(&watcher, &QDBusPendingCallWatcher::finished); co_return watcher.reply(); } qcoro-0.12.0/qcoro/dbus/qcorodbuspendingcall.h000066400000000000000000000064021477357142500213710ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include "qcorotask.h" #include "qcorodbus_export.h" #include class QDBusMessage; class QDBusPendingCall; /*! \cond internal */ namespace QCoro::detail { class QCORODBUS_EXPORT QCoroDBusPendingCall { private: class WaitForFinishedOperation { public: explicit WaitForFinishedOperation(const QDBusPendingCall &call); bool await_ready() const noexcept; void await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept; QDBusMessage await_resume() const; private: const QDBusPendingCall &mCall; }; const QDBusPendingCall &mCall; friend struct awaiter_type; public: //! Constructor. explicit QCoroDBusPendingCall(const QDBusPendingCall &call); /*! \brief Operation that allows co_awaiting completion of the pending DBus call. Waits until the DBus call is finished. This is equivalent to using [`QDBusPendingCallWatcher`][qdoc-qdbuspendingcallwatcher] and waiting for it to emit the [`finished()`][qdoc-qdbuspendingcallwatcher-finished] signal. Returns a `QDBusMessage` representing the reply to the call. If the call is already finished or has an error, the coroutine will not suspend and the `co_await` expression will return immediatelly. It is also possible to just directly use a `QDBusPendingCall` in a `co_await` expression to await its completion: ```cpp QDBusPendingCall pendingCall = interface.asyncCall(...); const auto reply = co_await pendingCall; ``` The above is equivalent to: ```cpp QDBusPendingCall pendingCall = interface.asyncCall(...); const auto reply = co_await qCoro(pendingCall).waitForFinished(); ``` This is a coroutine-friendly equivalent to using [`QDBusPendingCallWatcher`][qdoc-qdbuspendingcallwatcher]: ```cpp QDBusPendingCall call = interface.asyncCall(...); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [](QDBusPendingCallWatcher *watcher) { watcher->deleteLater(); const QDBusReply<...> reply = *watcher; ... }); ``` [qdoc-qdbuspendingcallwatcher]: https://doc.qt.io/qt-5/qdbuspendingcallwatcher.html [qdoc-qdbuspendingcallwatcher-finished]: https://doc.qt.io/qt-5/qdbuspendingcallwatcher.html#finished @see docs/reference/qdbuspendingcall.md */ Task waitForFinished(); }; template<> struct awaiter_type { using type = QCoroDBusPendingCall::WaitForFinishedOperation; }; } // namespace QCoro::detail /*! \endcond */ //! Returns a co_await-friendly wrapper for QDBusPendingCall object /*! * Returns a wrapper for QDBusPendingCall \c call that provides coroutine-friendly * way to co_await completion of the call. * * @see docs/reference/qdbuspendingcall.md */ inline auto qCoro(const QDBusPendingCall &call) { return QCoro::detail::QCoroDBusPendingCall{call}; } qcoro-0.12.0/qcoro/dbus/qcorodbuspendingreply.h000066400000000000000000000115041477357142500216100ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include "qcorotask.h" #include "qcorosignal.h" #include #include class QDBusMessage; /*! \cond internal */ namespace QCoro::detail { template class QCoroDBusPendingReply { private: #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // QDBusPendingReply is a variadic template since Qt6, but in Qt5 the // maximum number of template arguments was 8, so we simulate the Qt5 // behavior here. static_assert(sizeof...(Args) <= 8, "In Qt5 QDBusPendingReply has maximum 8 arguments."); #endif class WaitForFinishedOperation { public: explicit WaitForFinishedOperation(const QDBusPendingReply &reply) : mReply(reply) {} bool await_ready() const noexcept { return mReply.isFinished(); } void await_suspend(std::coroutine_handle<> awaitingCoroutine) { auto *watcher = new QDBusPendingCallWatcher{mReply}; QObject::connect(watcher, &QDBusPendingCallWatcher::finished, [awaitingCoroutine](auto *watcher) mutable { awaitingCoroutine.resume(); watcher->deleteLater(); }); } QDBusPendingReply await_resume() const { Q_ASSERT(mReply.isFinished()); return mReply; } private: QDBusPendingReply mReply; }; QDBusPendingReply mReply; friend struct awaiter_type>; public: //! Constructor. explicit QCoroDBusPendingReply(const QDBusPendingReply &reply) : mReply(reply) {} /*! \brief Operation that allows co_awaiting completion of the pending DBus reply. Waits until the DBus call is finished. This is equivalent to using [`QDBusPendingCallWatcher`][qdoc-qdbuspendingcallwatcher] and waiting for it to emit the [`finished()`][qdoc-qdbuspendingcallwatcher-finished] signal. Returns a `QDBusMessage` representing the received reply. If the reply is already finished or an error has occurred the coroutine will not suspend and will return a result immediatelly. This is a coroutine-friendly equivalent to using [`QDBusPendingCallWatcher`][qdoc-qdbuspendingcallwatcher]: ```cpp QDBusPendingCall call = interface.asyncCall(...); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [](QDBusPendingCallWatcher *watcher) { watcher->deleteLater(); const QDBusPendingReply<...> reply = *watcher; ... }); ``` It is also possible to just directly use a `QDBusPendingReply` in a `co_await` expression to await its completion: ```cpp QDBusPendingReply<...> pendingReply = interface.asyncCall(...); const auto reply = co_await pendingReply; ``` The above is equivalent to: ```cpp QDBusPendingReply<...> pendingReply = interface.asyncCall(...); const auto reply = co_await qCoro(pendingReply).waitForFinished(); ``` [qdoc-qdbuspendingcallwatcher]: https://doc.qt.io/qt-5/qdbuspendingcallwatcher.html [qdoc-qdbuspendingcallwatcher-finished]: https://doc.qt.io/qt-5/qdbuspendingcallwatcher.html#finished @see docs/reference/qdbuspendingreply.md */ Task> waitForFinished() { if (!mReply.isFinished()) { QDBusPendingCallWatcher watcher{mReply}; co_await qCoro(&watcher, &QDBusPendingCallWatcher::finished); co_return watcher.reply(); } co_return mReply; } }; template struct awaiter_type> { using type = typename QCoroDBusPendingReply::WaitForFinishedOperation; }; } // namespace QCoro::detail /*! \endcond */ //! Returns a coroutine-friendly wrapper for a QDBusPendingReply object. /*! * Returns a wrapper for the QDBusPendingReply \c reply that provides * a coroutine-friendly way to await the completion of the pending reply. * * Note that is is also possible to just directly `co_await` the `QDBusPendingReply` * completion without using the wrapper class: * * ``` * QDBusPendingReply<...> pendingReply = interface.asyncCall(...); * const auto reply = co_await pendingReply; * ``` * * @see docs/reference/qdbuspendingreply.md */ template inline auto qCoro(const QDBusPendingReply &reply) { return QCoro::detail::QCoroDBusPendingReply{reply}; } qcoro-0.12.0/qcoro/impl/000077500000000000000000000000001477357142500150205ustar00rootroot00000000000000qcoro-0.12.0/qcoro/impl/connect.h000066400000000000000000000044361477357142500166310ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 Daniel Vrátil // // SPDX-License-Identifier: MIT /* * Do NOT include this file directly - include the QCoroTask header instead! */ #pragma once #include "../qcorotask.h" #include namespace QCoro { template requires std::is_invocable_v || std::is_invocable_v || std::is_invocable_v || std::is_invocable_v inline void connect(QCoro::Task &&task, QObjectSubclass *context, Callback func) { QPointer ctxWatcher = context; if constexpr (std::is_same_v) { task.then([ctxWatcher, func = std::move(func)]() { if (ctxWatcher) { if constexpr (std::is_member_function_pointer_v) { (ctxWatcher->*func)(); } else { func(); } } }); } else { task.then([ctxWatcher, func = std::move(func)](auto &&value) { if (ctxWatcher) { if constexpr (std::is_invocable_v) { (ctxWatcher->*func)(std::forward(value)); } else if constexpr (std::is_invocable_v) { func(std::forward(value)); } else { Q_UNUSED(value); if constexpr (std::is_member_function_pointer_v) { (ctxWatcher->*func)(); } else { func(); } } } }); } } template requires detail::TaskConvertible && (std::is_invocable_v || std::is_invocable_v> || std::is_invocable_v || std::is_invocable_v>) && (!detail::isTask_v) inline void connect(T &&future, QObjectSubclass *context, Callback func) { auto task = detail::toTask(std::move(future)); connect(std::move(task), context, func); } } // namespace QCoroqcoro-0.12.0/qcoro/impl/lazytask.h000066400000000000000000000036071477357142500170410ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include "qcorolazytask.h" #include "qcorotask.h" #ifndef NDEBUG #include #endif namespace QCoro { namespace detail { template inline LazyTask LazyTaskPromise::get_return_object() noexcept { return LazyTask(std::coroutine_handle::from_promise(*this)); } template inline std::suspend_always LazyTaskPromise::initial_suspend() const noexcept { return {}; } } // namespace detail template inline LazyTask::~LazyTask() { #ifndef NDEBUG if (this->mCoroutine && !this->mCoroutine.done()) { qWarning() << "QCoro::LazyTask destroyed before it was awaited!"; } #endif } template inline auto LazyTask::operator co_await() const noexcept { //! Specialization of the TaskAwaiterBase that returns the promise result by value class TaskAwaiter : public detail::TaskAwaiterBase { public: TaskAwaiter(std::coroutine_handle thisTask) : detail::TaskAwaiterBase{thisTask} {} auto await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept { detail::TaskAwaiterBase::await_suspend(awaitingCoroutine); // Return handle to the lazy task, so that it gets automatically resumed. return this->mAwaitedCoroutine; } auto await_resume() { Q_ASSERT(this->mAwaitedCoroutine); if constexpr (!std::is_void_v) { return std::move(this->mAwaitedCoroutine.promise().result()); } else { // Wil re-throw exception, if any is stored this->mAwaitedCoroutine.promise().result(); } } }; return TaskAwaiter{this->mCoroutine}; } } // namespace QCoro qcoro-0.12.0/qcoro/impl/task.h000066400000000000000000000004121477357142500161300ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 Daniel Vrátil // // SPDX-License-Identifier: MIT /* * Do NOT include this file directly - include the QCoroTask header instead! */ #pragma once #include "../qcorotask.h" namespace QCoro { } // namespace QCoroqcoro-0.12.0/qcoro/impl/taskawaiterbase.h000066400000000000000000000017671477357142500203560ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 Daniel Vrátil // // SPDX-License-Identifier: MIT /* * Do NOT include this file directly - include the QCoroTask header instead! */ #pragma once #include "../qcorotask.h" #include namespace QCoro::detail { template inline bool TaskAwaiterBase::await_ready() const noexcept { return mAwaitedCoroutine && mAwaitedCoroutine.done(); } template inline void TaskAwaiterBase::await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept { if (!mAwaitedCoroutine) { qWarning() << "QCoro::Task: Awaiting a default-constructed or a moved-from QCoro::Task<> - this will hang forever!"; return; } mAwaitedCoroutine.promise().addAwaitingCoroutine(awaitingCoroutine); } template inline TaskAwaiterBase::TaskAwaiterBase(std::coroutine_handle awaitedCoroutine) : mAwaitedCoroutine(awaitedCoroutine) {} } // namespace QCoro::detailqcoro-0.12.0/qcoro/impl/taskbase.h000066400000000000000000000206131477357142500167700ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 Daniel Vrátil // // SPDX-License-Identifier: MIT /* * Do NOT include this file directly - include the QCoroTask header instead! */ #pragma once #include "../qcorotask.h" #include #include namespace QCoro::detail { template class TaskImpl, typename PromiseType> inline TaskBase::TaskBase(std::coroutine_handle coroutine) : mCoroutine(coroutine) { mCoroutine.promise().refCoroutine(); } template class TaskImpl, typename PromiseType> inline TaskBase::TaskBase(TaskBase &&other) noexcept : mCoroutine(other.mCoroutine) { other.mCoroutine = nullptr; } //! The task can be move-assigned. template class TaskImpl, typename PromiseType> inline auto TaskBase::operator=(TaskBase &&other) noexcept -> TaskBase & { if (std::addressof(other) != this) { if (mCoroutine) { mCoroutine.promise().derefCoroutine(); } mCoroutine = other.mCoroutine; other.mCoroutine = nullptr; } return *this; } template class TaskImpl, typename PromiseType> inline TaskBase::~TaskBase() { if (mCoroutine) { mCoroutine.promise().derefCoroutine(); } } template class TaskImpl, typename PromiseType> inline bool TaskBase::isReady() const { return !mCoroutine || mCoroutine.done(); } template class TaskImpl, typename PromiseType> inline auto TaskBase::operator co_await() const noexcept { //! Specialization of the TaskAwaiterBase that returns the promise result by value class TaskAwaiter : public detail::TaskAwaiterBase { public: TaskAwaiter(std::coroutine_handle awaitedCoroutine) : detail::TaskAwaiterBase{awaitedCoroutine} {} //! Called when the co_awaited coroutine is resumed. /* * \return the result from the coroutine's promise, factically the * value co_returned by the coroutine. */ auto await_resume() { Q_ASSERT(this->mAwaitedCoroutine); if constexpr (!std::is_void_v) { return std::move(this->mAwaitedCoroutine.promise().result()); } else { // Wil re-throw exception, if any is stored this->mAwaitedCoroutine.promise().result(); } } }; return TaskAwaiter{this->mCoroutine}; } template class TaskImpl, typename PromiseType> template requires (std::is_invocable_v || (!std::is_void_v && std::is_invocable_v)) inline auto TaskBase::then(ThenCallback &&callback) & { // Provide a custom error handler that simply re-throws the current exception return thenImplRef(*this, std::forward(callback), [](const auto &) { throw; }); } template class TaskImpl, typename PromiseType> template requires (std::is_invocable_v || (!std::is_void_v && std::is_invocable_v)) inline auto TaskBase::then(ThenCallback &&callback) && { // When chaining LazyTask continuation to a temporary (xvalue) LazyTask, the temporary LazyTask must be captured in // thenImpl() by value as (as an argument), since thenImpl() is suspended immediately (it's a lazy task) and the temporary // would be destroyed before the thenImpl() is resumed and has a chance to consume/co_await the temporary LazyTask. return thenImpl(std::move(*this), std::forward(callback), [](const auto &) { throw; }); } template class TaskImpl, typename PromiseType> template requires ((std::is_invocable_v || (!std::is_void_v && std::is_invocable_v)) && std::is_invocable_v) inline auto TaskBase::then(ThenCallback &&callback, ErrorCallback &&errorCallback) & { return thenImplRef(*this, std::forward(callback), std::forward(errorCallback)); } template class TaskImpl, typename PromiseType> template requires ((std::is_invocable_v || (!std::is_void_v && std::is_invocable_v)) && std::is_invocable_v) inline auto TaskBase::then(ThenCallback &&callback, ErrorCallback &&errorCallback) && { return thenImpl(std::move(*this), std::forward(callback), std::forward(errorCallback)); } template class TaskImpl, typename PromiseType> template inline auto TaskBase::invokeCb(ThenCallback &&callback, [[maybe_unused]] Args && ... args) { if constexpr (std::is_invocable_v) { return callback(std::forward(args) ...); } else { return callback(); } } template class TaskImpl, typename PromiseType> template inline auto TaskBase::handleException(ErrorCallback &errCb, const std::exception &exception) -> U { errCb(exception); if constexpr (!std::is_void_v) { return U{}; } } template class TaskImpl, typename PromiseType> template inline auto TaskBase::thenImpl(TaskT task_, ThenCallback &&thenCallback, ErrorCallback &&errorCallback) -> std::conditional_t, R, TaskImpl> { auto thenCb = std::forward(thenCallback); auto errCb = std::forward(errorCallback); const auto &task = static_cast &>(task_); if constexpr (std::is_void_v::value_type>) { try { co_await task; } catch (const std::exception &e) { co_return handleException(errCb, e); } if constexpr (detail::isTask_v) { co_return co_await invokeCb(thenCb); } else { co_return invokeCb(thenCb); } } else { std::optional value; try { value.emplace(std::move(co_await task)); } catch (const std::exception &e) { co_return handleException(errCb, e); } if constexpr (detail::isTask_v) { co_return co_await invokeCb(thenCb, std::move(*value)); } else { co_return invokeCb(thenCb, std::move(*value)); } } } template class TaskImpl, typename PromiseType> template inline auto TaskBase::thenImplRef(TaskT &task_, ThenCallback &&thenCallback, ErrorCallback &&errorCallback) -> std::conditional_t, R, TaskImpl> { auto thenCb = std::forward(thenCallback); auto errCb = std::forward(errorCallback); const auto &task = static_cast &>(task_); if constexpr (std::is_void_v::value_type>) { try { co_await task; } catch (const std::exception &e) { co_return handleException(errCb, e); } if constexpr (detail::isTask_v) { co_return co_await invokeCb(thenCb); } else { co_return invokeCb(thenCb); } } else { std::optional value; try { value.emplace(std::move(co_await task)); } catch (const std::exception &e) { co_return handleException(errCb, e); } if constexpr (detail::isTask_v) { co_return co_await invokeCb(thenCb, std::move(*value)); } else { co_return invokeCb(thenCb, std::move(*value)); } } } } // namespace QCoro::detail qcoro-0.12.0/qcoro/impl/taskfinalsuspend.h000066400000000000000000000017651477357142500205600ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 Daniel Vrátil // // SPDX-License-Identifier: MIT /* * Do NOT include this file directly - include the QCoroTask header instead! */ #pragma once #include "../qcorotask.h" namespace QCoro::detail { inline TaskFinalSuspend::TaskFinalSuspend(const std::vector> &awaitingCoroutines) : mAwaitingCoroutines(awaitingCoroutines) {} inline bool TaskFinalSuspend::await_ready() const noexcept { return false; } template inline void TaskFinalSuspend::await_suspend(std::coroutine_handle finishedCoroutine) noexcept { auto &promise = finishedCoroutine.promise(); for (auto &awaiter : mAwaitingCoroutines) { awaiter.resume(); } mAwaitingCoroutines.clear(); // The handle will be destroyed here only if the associated Task has already been destroyed promise.derefCoroutine(); } constexpr void TaskFinalSuspend::await_resume() const noexcept {} } // namespace QCoro::detail qcoro-0.12.0/qcoro/impl/taskpromise.h000066400000000000000000000040471477357142500175370ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 Daniel Vrátil // // SPDX-License-Identifier: MIT /* * Do NOT include this file directly - include the QCoroTask header instead! */ #pragma once #include "../qcorotask.h" #include // for Q_ASSERT namespace QCoro::detail { template inline Task TaskPromise::get_return_object() noexcept { return Task{std::coroutine_handle::from_promise(*this)}; } template inline void TaskPromise::unhandled_exception() { mValue = std::current_exception(); } template inline void TaskPromise::return_value(T &&value) noexcept { mValue.template emplace(std::forward(value)); } template inline void TaskPromise::return_value(const T &value) noexcept { mValue = value; } template template requires QCoro::concepts::constructible_from inline void TaskPromise::return_value(U &&value) noexcept { mValue = T(std::move(value)); } template inline T &TaskPromise::result() & { if (std::holds_alternative(mValue)) { Q_ASSERT(std::get(mValue) != nullptr); std::rethrow_exception(std::get(mValue)); } return std::get(mValue); } template inline T &&TaskPromise::result() && { if (std::holds_alternative(mValue)) { Q_ASSERT(std::get(mValue) != nullptr); std::rethrow_exception(std::get(mValue)); } return std::move(std::get(mValue)); } inline Task TaskPromise::get_return_object() noexcept { return Task{std::coroutine_handle::from_promise(*this)}; } inline void TaskPromise::unhandled_exception() { mException = std::current_exception(); } inline void TaskPromise::return_void() noexcept {} inline void TaskPromise::result() { if (mException) { std::rethrow_exception(mException); } } } // namespace QCoro::detailqcoro-0.12.0/qcoro/impl/taskpromisebase.h000066400000000000000000000030431477357142500203650ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 Daniel Vrátil // // SPDX-License-Identifier: MIT /* * Do NOT include this file directly - include the QCoroTask header instead! */ #pragma once #include "../qcorotask.h" #include namespace QCoro::detail { inline TaskPromiseBase::TaskPromiseBase() : mRefCount(1) { } inline std::suspend_never TaskPromiseBase::initial_suspend() const noexcept { return {}; } inline auto TaskPromiseBase::final_suspend() const noexcept { return TaskFinalSuspend{mAwaitingCoroutines}; } template inline auto TaskPromiseBase::await_transform(T &&value) { return Awaiter{std::forward(value)}; } template inline auto && TaskPromiseBase::await_transform(T &&awaitable) { return std::forward(awaitable); } template inline auto &TaskPromiseBase::await_transform(T &awaitable) { return awaitable; } inline void TaskPromiseBase::addAwaitingCoroutine(std::coroutine_handle<> awaitingCoroutine) { mAwaitingCoroutines.push_back(awaitingCoroutine); } inline bool TaskPromiseBase::hasAwaitingCoroutine() const { return !mAwaitingCoroutines.empty(); } inline void TaskPromiseBase::derefCoroutine() { if (--mRefCount == 0) { destroyCoroutine(); } } inline void TaskPromiseBase::refCoroutine() { ++mRefCount; } inline void TaskPromiseBase::destroyCoroutine() { mRefCount = 0; auto handle = std::coroutine_handle::from_promise(*this); handle.destroy(); } } // namespace QCoro::detail qcoro-0.12.0/qcoro/impl/waitfor.h000066400000000000000000000060001477357142500166400ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 Daniel Vrátil // // SPDX-License-Identifier: MIT /* * Do NOT include this file directly - include the QCoroTask header instead! */ #pragma once #include "../qcorotask.h" #include #include namespace QCoro { namespace detail { template requires TaskConvertible auto toTask(Awaitable &&future) -> QCoro::Task> { co_return co_await future; } struct WaitContext { QEventLoop loop; bool coroutineFinished = false; std::exception_ptr exception; }; //! Helper class to run a coroutine in a nested event loop. /*! * We cannot just use QTimer or QMetaObject::invokeMethod() to schedule the func lambda to be * invoked from an event loop, because internally, Qt deallocates some structures when the * lambda returns, which causes invalid memory access and potentially double-free corruption * because the coroutine returns twice - once on suspend and once when it really finishes. * So instead we do basically what Qt does internally, but we make sure to not delete th * QFunctorSlotObjectWithNoArgs until after the event loop quits. */ template Task<> runCoroutine(WaitContext &context, Awaitable &&awaitable) { try { co_await awaitable; } catch (...) { context.exception = std::current_exception(); } context.coroutineFinished = true; context.loop.quit(); } template Task<> runCoroutine(WaitContext &context, std::optional &result, Awaitable &&awaitable) { try { result.emplace(co_await awaitable); } catch (...) { context.exception = std::current_exception(); } context.coroutineFinished = true; context.loop.quit(); } template T waitFor(Awaitable &&awaitable) { WaitContext context; if constexpr (std::is_void_v) { runCoroutine(context, std::forward(awaitable)); if (!context.coroutineFinished) { context.loop.exec(); } if (context.exception) { std::rethrow_exception(context.exception); } } else { std::optional result; runCoroutine(context, result, std::forward(awaitable)); if (!context.coroutineFinished) { context.loop.exec(); } if (context.exception) { std::rethrow_exception(context.exception); } return std::move(*result); } } } // namespace detail template inline T waitFor(QCoro::Task &task) { return detail::waitFor(std::forward>(task)); } template inline T waitFor(QCoro::Task &&task) { return detail::waitFor(std::forward>(task)); } template inline auto waitFor(Awaitable &&awaitable) { return detail::waitFor>(std::forward(awaitable)); } } // namespace QCoroqcoro-0.12.0/qcoro/macros_p.h000066400000000000000000000006221477357142500160330ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #ifndef QCORO_DEFAULT_MOVE #define QCORO_DEFAULT_MOVE(Class) \ Class(Class &&) noexcept = default; \ Class &operator=(Class &&) noexcept = default; #endif qcoro-0.12.0/qcoro/network/000077500000000000000000000000001477357142500155505ustar00rootroot00000000000000qcoro-0.12.0/qcoro/network/CMakeLists.txt000066400000000000000000000010001477357142500202770ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2021 Daniel Vrátil # # SPDX-License-Identifier: MIT add_qcoro_library( NAME Network SOURCES qcoroabstractsocket.cpp qcorolocalsocket.cpp qcoronetworkreply.cpp qcorotcpserver.cpp CAMELCASE_HEADERS QCoroNetwork QCoroAbstractSocket QCoroLocalSocket QCoroNetworkReply QCoroTcpServer QCORO_LINK_LIBRARIES PUBLIC Coro Core QT_LINK_LIBRARIES PUBLIC Core Network ) qcoro-0.12.0/qcoro/network/qcoroabstractsocket.cpp000066400000000000000000000104701477357142500223360ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcoroabstractsocket.h" #include "qcoroiodevice_p.h" #include "qcorosignal.h" using namespace QCoro::detail; using namespace std::chrono_literals; namespace { class AbstractSocketReadySignalHelper : public WaitSignalHelper { Q_OBJECT public: explicit AbstractSocketReadySignalHelper(const QAbstractSocket *socket, void(QIODevice::*readySignal)()) : WaitSignalHelper(socket, readySignal) , mStateChanged(connect(socket, &QAbstractSocket::stateChanged, this, [this](QAbstractSocket::SocketState state) { handleStateChange(state, false); })) {} explicit AbstractSocketReadySignalHelper(const QAbstractSocket *socket, void(QIODevice::*readySignal)(qint64)) : WaitSignalHelper(socket, readySignal) , mStateChanged(connect(socket, &QAbstractSocket::stateChanged, this, [this](QAbstractSocket::SocketState state) { handleStateChange(state, static_cast(0)); })) {} private: template void handleStateChange(QAbstractSocket::SocketState state, T result) { if (state == QAbstractSocket::ClosingState || state == QAbstractSocket::UnconnectedState) { disconnect(mStateChanged); emitReady(result); } } private: QMetaObject::Connection mStateChanged; }; } // namespace QCoroAbstractSocket::QCoroAbstractSocket(QAbstractSocket *socket) : QCoroIODevice(socket) {} QCoro::Task> QCoroAbstractSocket::waitForReadyReadImpl(std::chrono::milliseconds timeout) { const auto *socket = static_cast(mDevice.data()); if (socket->state() != QAbstractSocket::ConnectedState) { co_return false; } AbstractSocketReadySignalHelper helper(socket, &QIODevice::readyRead); co_return co_await qCoro(&helper, qOverload(&WaitSignalHelper::ready), timeout); } QCoro::Task> QCoroAbstractSocket::waitForBytesWrittenImpl(std::chrono::milliseconds timeout) { const auto *socket = static_cast(mDevice.data()); if (socket->state() != QAbstractSocket::ConnectedState) { co_return std::nullopt; } AbstractSocketReadySignalHelper helper(socket, &QIODevice::bytesWritten); co_return co_await qCoro(&helper, qOverload(&WaitSignalHelper::ready), timeout); } QCoro::Task QCoroAbstractSocket::waitForConnected(int timeout_msecs) { return waitForConnected(std::chrono::milliseconds{timeout_msecs}); } QCoro::Task QCoroAbstractSocket::waitForConnected(std::chrono::milliseconds timeout) { const auto *socket = static_cast(mDevice.data()); if (socket->state() == QAbstractSocket::ConnectedState) { co_return true; } const auto result = co_await qCoro(socket, &QAbstractSocket::connected, timeout); co_return result.has_value(); } QCoro::Task QCoroAbstractSocket::waitForDisconnected(int timeout_msecs) { return waitForDisconnected(std::chrono::milliseconds{timeout_msecs}); } QCoro::Task QCoroAbstractSocket::waitForDisconnected(std::chrono::milliseconds timeout) { const auto *socket = static_cast(mDevice.data()); if (socket->state() == QAbstractSocket::UnconnectedState) { co_return false; } const auto result = co_await qCoro(socket, &QAbstractSocket::disconnected, timeout); co_return result.has_value(); } QCoro::Task QCoroAbstractSocket::connectToHost(const QString &hostName, quint16 port, QIODevice::OpenMode openMode, QAbstractSocket::NetworkLayerProtocol protocol, std::chrono::milliseconds timeout) { static_cast(mDevice.data())->connectToHost(hostName, port, openMode, protocol); return waitForConnected(timeout); } QCoro::Task QCoroAbstractSocket::connectToHost(const QHostAddress &address, quint16 port, QIODevice::OpenMode openMode, std::chrono::milliseconds timeout) { static_cast(mDevice.data())->connectToHost(address, port, openMode); return waitForConnected(timeout); } #include "qcoroabstractsocket.moc"qcoro-0.12.0/qcoro/network/qcoroabstractsocket.h000066400000000000000000000137471477357142500220150ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include "waitoperationbase_p.h" #include "qcoroiodevice.h" #include "qcoronetwork_export.h" #include #include #include class QAbstractSocket; namespace QCoro::detail { using namespace std::chrono_literals; //! QAbstractSocket wrapper with co_awaitable-friendly API. class QCORONETWORK_EXPORT QCoroAbstractSocket final : public QCoroIODevice { public: explicit QCoroAbstractSocket(QAbstractSocket *socket); //! Co_awaitable equivalent to [`QAbstractSocket::waitForConnected()`][qtdoc-qabstractsocket-waitForConnected]. /*! * Waits until the socket is connected, up to \c timeout_msecs milliseconds. If the connection has been * established, the coroutine returns `true`, otherwise it retuns `false`. * * If the timeout is -1, the operation will never time out. * * [qtdoc-qabstractsocket-waitForConnected]: https://doc.qt.io/qt-5/qabstractsocket.html#waitForConnected */ Task waitForConnected(int timeout_msecs = 30'000); //! Co_awaitable equivalent to [`QAbstractSocket::waitForConnected()`][qtdoc-qabstractsocket-waitForConnected]. /*! * Waits until the socket is connected, up to \c timeout_msecs milliseconds. If the connection has been * established, the coroutine returns `true`, otherwise it retuns `false`. * * Unlike the Qt version, this overload uses `std::chrono::milliseconds` to express the * timeout rather than plain `int`. If the timeout is -1, the operation will never time out. * * * [qtdoc-qabstractsocket-waitForConnected]: https://doc.qt.io/qt-5/qabstractsocket.html#waitForConnected */ Task waitForConnected(std::chrono::milliseconds timeout); //! Co_awaitable equivalent to [`QAbstractSocket::waitForDisconnected()`][qtdoc-qabstractsocket-waitForDisconnected]. /*! * Waits until the socket has disconnected, up to \c timeout_msecs milliseconds. If the connection was * successfully disconnected, returns `true`, otherwise returns `false` (if the operation timed out, * if an error occurred or if the `QAbstractSocket` is already disconnected). * * If the timeout is -1, the operation will never time out. * * [qtdoc-qabstractsocket-waitForDisconnected]: https://doc.qt.io/qt-5/qabstractsocket.hmtl#waitForDisconnected */ Task waitForDisconnected(int timeout_msecs = 30'000); //! Co_awaitable equivalent to [`QAbstractSocket::waitForDisconnected()`][qtdoc-qabstractsocket-waitForDisconnected]. /*! * Waits until the socket has disconnected, up to \c timeout_msecs milliseconds. If the connection was * successfully disconnected, returns `true`, otherwise returns `false` (if the operation timed out, * if an error occurred or if the `QAbstractSocket` is already disconnected). * * Unlike the Qt version, this overload uses `std::chrono::milliseconds` to express the * timeout rather than plain `int`. If the timeout is -1, the operation will never time out. * * [qtdoc-qabstractsocket-waitForDisconnected]: https://doc.qt.io/qt-5/qabstractsocket.hmtl#waitForDisconnected */ Task waitForDisconnected(std::chrono::milliseconds timeout); //! Connects to server and waits until the connection is established. /*! * Equivalent to calling [`QAbstractSocket::connecToServer`][qdoc-qabstractsocket-connecToHost] * followed by [`QAbstractSocket::waitForConnected`][qdoc-qabstractsocket-waitForConnected]. * * Returns `true` if the connection has been successfully established withint he given timeout, * `false` otherwise. If the timeout is -1, the operation will never time out. * * [qtdoc-qabstractsocket-connectToHost]: https://doc.qt.io/qt-5/qabstractsocket.html#connectToHost * [qtdoc-qabstractsocket-waitForConnected]: https://doc.qt.io/qt-5/qabstractsocket.html#waitForConnected */ Task connectToHost(const QString &hostName, quint16 port, QIODevice::OpenMode openMode = QIODevice::ReadWrite, QAbstractSocket::NetworkLayerProtocol protocol = QAbstractSocket::AnyIPProtocol, std::chrono::milliseconds timeout = std::chrono::seconds{30}); //! Connects to server and waits until the connection is established. /*! * Equivalent to calling [`QAbstractSocket::connecToServer`][qdoc-qabstractsocket-connecToHost-1] * followed by [`QAbstractSocket::waitForConnected`][qdoc-qabstractsocket-waitForConnected]. * * Returns `true` if the connection has been successfully established within the given timeout, * `false` otherwise. If the timeout is -1, the operation will never time out. * * [qtdoc-qabstractsocket-waitForConnected]: https://doc.qt.io/qt-5/qabstractsocket.html#waitForConnected * [qtdoc-qabstractsocket-connectToHost-1]: https://doc.qt.io/qt-5/qabstractsocket.html#connectToHost-1 */ Task connectToHost(const QHostAddress &address, quint16 port, QIODevice::OpenMode openMode = QIODevice::ReadWrite, std::chrono::milliseconds timeout = std::chrono::seconds{30}); private: Task> waitForReadyReadImpl(std::chrono::milliseconds timeout) override; Task> waitForBytesWrittenImpl(std::chrono::milliseconds timeout) override; }; } // namespace QCoro::detail //! Returns a coroutine-friendly wrapper for QAbstractSocket object. /*! * Returns a wrapper for the QAbstractSocket \c s that provides coroutine-friendly * way to co_await the socket to connect and disconnect. * * @see docs/reference/qabstractsocket.md */ inline auto qCoro(QAbstractSocket &s) noexcept { return QCoro::detail::QCoroAbstractSocket{&s}; } //! \copydoc qCoro(QAbstractSocket &s) noexcept inline auto qCoro(QAbstractSocket *s) noexcept { return QCoro::detail::QCoroAbstractSocket{s}; } qcoro-0.12.0/qcoro/network/qcorolocalsocket.cpp000066400000000000000000000113561477357142500216310ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcorolocalsocket.h" #include "qcorosignal.h" #include "qcoroiodevice_p.h" #include #include using namespace QCoro::detail; using namespace std::chrono_literals; namespace { class SocketConnectedHelper : public QObject { Q_OBJECT public: SocketConnectedHelper(const QLocalSocket *socket, void(QLocalSocket::*signal)()) : QObject() , mSignal(connect(socket, signal, this, [this]() { emitReady(true); })) , mStateChange(connect(socket, &QLocalSocket::stateChanged, this, [this](auto state) { if (state == QLocalSocket::UnconnectedState) { emitReady(false); } })) {} Q_SIGNALS: void ready(bool result); private: void emitReady(bool result) { disconnect(mSignal); disconnect(mStateChange); Q_EMIT ready(result); } private: QMetaObject::Connection mSignal; QMetaObject::Connection mStateChange; }; class LocalSocketReadySignalHelper : public WaitSignalHelper { Q_OBJECT public: LocalSocketReadySignalHelper(const QLocalSocket *socket, void(QIODevice::*signal)()) : WaitSignalHelper(socket, signal) , mStateChange(connect(socket, &QLocalSocket::stateChanged, this, [this](auto state) { stateChanged(state, false); })) {} LocalSocketReadySignalHelper(const QLocalSocket *socket, void(QIODevice::*signal)(qint64)) : WaitSignalHelper(socket, signal) , mStateChange(connect(socket, &QLocalSocket::stateChanged, this, [this](auto state) { stateChanged(state, static_cast(0)); })) {} private: template void stateChanged(QLocalSocket::LocalSocketState state, T result) { if (state != QLocalSocket::ConnectedState) { disconnect(mStateChange); emitReady(result); } } private: QMetaObject::Connection mStateChange; }; } // namespace QCoroLocalSocket::QCoroLocalSocket(QLocalSocket *socket) : QCoroIODevice(socket) {} QCoro::Task> QCoroLocalSocket::waitForReadyReadImpl(std::chrono::milliseconds timeout) { const auto *socket = static_cast(mDevice.data()); if (socket->state() != QLocalSocket::ConnectedState) { co_return false; } LocalSocketReadySignalHelper helper(socket, &QLocalSocket::readyRead); co_return co_await qCoro(&helper, qOverload(&LocalSocketReadySignalHelper::ready), timeout); } QCoro::Task> QCoroLocalSocket::waitForBytesWrittenImpl(std::chrono::milliseconds timeout) { const auto *socket = static_cast(mDevice.data()); if (socket->state() != QLocalSocket::ConnectedState) { co_return std::nullopt; } LocalSocketReadySignalHelper helper(socket, &QLocalSocket::bytesWritten); co_return co_await qCoro(&helper, qOverload(&LocalSocketReadySignalHelper::ready), timeout); } QCoro::Task QCoroLocalSocket::waitForConnected(int timeout_msecs) { return waitForConnected(std::chrono::milliseconds(timeout_msecs)); } QCoro::Task QCoroLocalSocket::waitForConnected(std::chrono::milliseconds timeout) { const auto *socket = static_cast(mDevice.data()); if (socket->state() == QLocalSocket::ConnectedState) { co_return true; } SocketConnectedHelper helper(socket, &QLocalSocket::connected); const auto result = co_await qCoro(&helper, &SocketConnectedHelper::ready, timeout); co_return result.value_or(false); } QCoro::Task QCoroLocalSocket::waitForDisconnected(int timeout_msecs) { return waitForDisconnected(std::chrono::milliseconds(timeout_msecs)); } QCoro::Task QCoroLocalSocket::waitForDisconnected(std::chrono::milliseconds timeout) { const auto *socket = static_cast(mDevice.data()); if (socket->state() == QLocalSocket::UnconnectedState) { co_return false; } const auto result = co_await qCoro(socket, &QLocalSocket::disconnected, timeout); co_return result.has_value(); } QCoro::Task QCoroLocalSocket::connectToServer(QIODevice::OpenMode openMode, std::chrono::milliseconds timeout) { static_cast(mDevice.data())->connectToServer(openMode); return waitForConnected(timeout); } QCoro::Task QCoroLocalSocket::connectToServer(const QString &name, QIODevice::OpenMode openMode, std::chrono::milliseconds timeout) { static_cast(mDevice.data())->connectToServer(name, openMode); return waitForConnected(timeout); } #include "qcorolocalsocket.moc"qcoro-0.12.0/qcoro/network/qcorolocalsocket.h000066400000000000000000000131111477357142500212650ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include "waitoperationbase_p.h" #include "qcoroiodevice.h" #include "qcoronetwork_export.h" #include #include namespace QCoro::detail { using namespace std::chrono_literals; //! QLocalSocket wrapper with co_awaitable-friendly API. class QCORONETWORK_EXPORT QCoroLocalSocket : public QCoroIODevice { public: explicit QCoroLocalSocket(QLocalSocket *socket); /** * \brief Co_awaitable equivalent to [`QLocalSocket::waitForConnected()`][qtdoc-qlocalsocket-waitForConnected]. * * Waits for at most \c timeout_msecs milliseconds. If the timeout is -1, the call will never timeout. * * \return Returns `true` when successfully connected, `false` is an error occured or the connection * wasn't established within the given timeout. * * [qtdoc-qlocalsocket-waitForConnected]: https://doc.qt.io/qt-5/qlocalsocket.html#waitForConnected */ Task waitForConnected(int timeout_msecs = 30'000); /** * \brief Co_awaitable equivalent to [`QLocalSocket::waitForConnected()`][qtdoc-qlocalsocket-waitForConnected]. * * Unlike the Qt version, this overload uses `std::chrono::milliseconds` to express the * timeout rather than plain `int`. If the \c timeout is -1, the call will never time out. * * \return Returns `true` when successfully connected, `false` is an error occured or the connection * wasn't established within the given timeout. * * [qtdoc-qlocalsocket-waitForConnected]: https://doc.qt.io/qt-5/qlocalsocket.html#waitForConnected */ Task waitForConnected(std::chrono::milliseconds timeout); /** * \brief Co_awaitable equivalent to [`QLocalSocket::waitForDisconnected()`][qtdoc-qlocalsocket-waitForDisconnected]. * * Waits for at most \c timeout_msecds milliseconds. If the timeout is -1, the call will never time out. * * \returns Returns `true` when the socket has been disconnected successfully. If the socket * wasn't connected, or doesn't disconnected within the specified timeout, the coroutine returns * `false`. * * [qtdoc-qlocalsocket-waitForDisconnected]: https://doc.qt.io/qt-5/qlocalsocket.hmtl#waitForDisconnected */ Task waitForDisconnected(int timeout_msecs = 30'000); /** * \brief Co_awaitable equivalent to [`QLocalSocket::waitForDisconnected()`][qtdoc-qlocalsocket-waitForDisconnected]. * * Unlike the Qt version, this overload uses `std::chrono::milliseconds` to express the * timeout rather than plain `int`. If the \c timeout is -1, the call will never time out. * * \returns Returns `true` when the socket has been disconnected successfully. If the socket * wasn't connected, or doesn't disconnected within the specified timeout, the coroutine returns * `false`. * * [qtdoc-qlocalsocket-waitForDisconnected]: https://doc.qt.io/qt-5/qlocalsocket.hmtl#waitForDisconnected */ Task waitForDisconnected(std::chrono::milliseconds timeout); /** * \brief Connects to server and waits until the connection is established. * * Equivalent to calling [`QLocalSocket::connecToServer`][qdoc-qlocalsocket-connecToServer] * followed by [`QLocalSocket::waitForConnected`][qdoc-qlocalsocket-waitForConnected]. * * Waits for at most \c timeout milliseconds. If the \c timeout is -1, the call will never time out. * * \return Returns `true` when connection is successfully established, `false` otherwise. * * [qtdoc-qlocalsocket-connectToServer]: https://doc.qt.io/qt-5/qlocalsocket.html#connectToServer * [qtdoc-qlocalsocket-waitForConnected]: https://doc.qt.io/qt-5/qlocalsocket.html#waitForConnected */ Task connectToServer(QIODevice::OpenMode openMode = QIODevice::ReadWrite, std::chrono::milliseconds timeout = std::chrono::seconds{30}); /** * \brief Connects to server and waits until the connection is established. * * Equivalent to calling [`QLocalSocket::connecToServer`][qdoc-qlocalsocket-connecToServer] * followed by [`QLocalSocket::waitForConnected`][qdoc-qlocalsocket-waitForConnected]. * * Waits for at most \c timeout milliseconds. If the \c timeout is -1, the call will never time out. * * \returns Returns `true` when connection is successfully established, `false` otherwise. * * [qtdoc-qlocalsocket-connectToServer-1]: https://doc.qt.io/qt-5/qlocalsocket.html#connectToServer-1 * [qtdoc-qlocalsocket-waitForConnected]: https://doc.qt.io/qt-5/qlocalsocket.html#waitForConnected */ Task connectToServer(const QString &name, QIODevice::OpenMode openMode = QIODevice::ReadWrite, std::chrono::milliseconds timeout = std::chrono::seconds{30}); private: Task> waitForReadyReadImpl(std::chrono::milliseconds timeout) override; Task> waitForBytesWrittenImpl(std::chrono::milliseconds timeout) override; }; } // namespace QCoro::detail //! Returns a coroutine-friendly wrapper for QLocalSocket object. /*! * Returns a wrapper for the QLocalSocket \c s that provides coroutine-friendly * way to co_await the socket to connect and disconnect. * * @see docs/reference/qlocalsocket.md */ inline auto qCoro(QLocalSocket &s) noexcept { return QCoro::detail::QCoroLocalSocket{&s}; } //! \copydoc qCoro(QLocalSocket &s) noexcept inline auto qCoro(QLocalSocket *s) noexcept { return QCoro::detail::QCoroLocalSocket{s}; } qcoro-0.12.0/qcoro/network/qcoronetwork.h000066400000000000000000000003371477357142500204610ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcoroabstractsocket.h" #include "qcorolocalsocket.h" #include "qcoronetworkreply.h" #include "qcorotcpserver.h" qcoro-0.12.0/qcoro/network/qcoronetworkreply.cpp000066400000000000000000000076631477357142500221010ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcoronetworkreply.h" #include "qcoroiodevice_p.h" #include "qcorosignal.h" using namespace QCoro::detail; namespace { class ReplyWaitSignalHelper : public WaitSignalHelper { Q_OBJECT public: ReplyWaitSignalHelper(const QNetworkReply *reply, void(QIODevice::*signal)()) : WaitSignalHelper(reply, signal) , mError(connect(reply, &QNetworkReply::errorOccurred, this, [this]() { emitReady(false); }, Qt::QueuedConnection)) , mFinished(connect(reply, &QNetworkReply::finished, this, [this]() { emitReady(true); }, Qt::QueuedConnection)) {} ReplyWaitSignalHelper(const QNetworkReply *reply, void(QIODevice::*signal)(qint64)) : WaitSignalHelper(reply, signal) , mError(connect(reply, &QNetworkReply::errorOccurred, this, [this]() { emitReady(0LL); }, Qt::QueuedConnection)) , mFinished(connect(reply, &QNetworkReply::finished, this, [this]() { emitReady(0LL); }, Qt::QueuedConnection)) {} private: void cleanup() override { disconnect(mError); disconnect(mFinished); WaitSignalHelper::cleanup(); } QMetaObject::Connection mError; QMetaObject::Connection mFinished; }; } // namespace struct QCoroNetworkReply::WaitForFinishedOperation::Private { static_assert(sizeof(std::unique_ptr) + sizeof(void*) == sizeof(QPointer), "QCoroNetworkReply::WaitForFinishedOperation is not BC with previous version, see comment in header"); Private(QPointer reply) : reply(reply) { // Ensure the signal is emitted in the same thread as the reply, not on the thread // where the coroutine is resumed... if (reply) { dummy.moveToThread(reply->thread()); } } QPointer reply; QObject dummy; }; QCoroNetworkReply::WaitForFinishedOperation::WaitForFinishedOperation(QPointer reply) : d(std::make_unique(reply)) { } QCoroNetworkReply::WaitForFinishedOperation::~WaitForFinishedOperation() = default; bool QCoroNetworkReply::WaitForFinishedOperation::await_ready() const noexcept { return !d->reply || d->reply->isFinished(); } void QCoroNetworkReply::WaitForFinishedOperation::await_suspend(std::coroutine_handle<> awaitingCoroutine) { if (d->reply) { QObject::connect(d->reply, &QNetworkReply::finished, &d->dummy, [awaitingCoroutine]() mutable { awaitingCoroutine.resume(); }, Qt::QueuedConnection); } else { awaitingCoroutine.resume(); } } QNetworkReply *QCoroNetworkReply::WaitForFinishedOperation::await_resume() const noexcept { return d->reply; } QCoro::Task> QCoroNetworkReply::waitForReadyReadImpl(std::chrono::milliseconds timeout) { const auto *reply = static_cast(mDevice.data()); if (reply->isFinished()) { co_return true; } ReplyWaitSignalHelper helper(reply, &QNetworkReply::readyRead); co_return co_await qCoro(&helper, qOverload(&ReplyWaitSignalHelper::ready), timeout); } QCoro::Task> QCoroNetworkReply::waitForBytesWrittenImpl(std::chrono::milliseconds timeout) { const auto *reply = static_cast(mDevice.data()); if (reply->isFinished()) { co_return false; } ReplyWaitSignalHelper helper(reply, &QNetworkReply::bytesWritten); co_return co_await qCoro(&helper, qOverload(&ReplyWaitSignalHelper::ready), timeout); } QCoro::Task QCoroNetworkReply::waitForFinished(std::chrono::milliseconds timeout) { const auto *reply = static_cast(mDevice.data()); if (reply->isFinished()) { co_return true; } const auto result = co_await qCoro(reply, &QNetworkReply::finished, timeout); co_return result.has_value(); } #include "qcoronetworkreply.moc"qcoro-0.12.0/qcoro/network/qcoronetworkreply.h000066400000000000000000000051361477357142500215370ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include "qcoroiodevice.h" #include "qcoronetwork_export.h" #include namespace QCoro::detail { class QCORONETWORK_EXPORT QCoroNetworkReply final : public QCoroIODevice { private: class WaitForFinishedOperation final { public: explicit WaitForFinishedOperation(QPointer reply); ~WaitForFinishedOperation(); bool await_ready() const noexcept; void await_suspend(std::coroutine_handle<> awaitingCoroutine); QNetworkReply *await_resume() const noexcept; private: struct Private; std::unique_ptr d; // BC: This class used to have a QPointer, which is 2*sizeof(void*), // while std::unique_ptr is only sizeof(void*), so this dummy one is to ensure binary // compatibility of this class. // FIXME: Remove in 1.0 // Silly gcc 11, cannot detect the dummy is unused and warns about unused attribute #if __GNUC__ > 11 || defined(__clang__) || defined(_MSC_VER) [[maybe_unused]] #endif void *dummy = nullptr; }; friend struct awaiter_type; public: using QCoroIODevice::QCoroIODevice; /** * \brief Waits for the reply to finish. * * Waits for at most \c timeout milliseconds. If the \c timeout is -1, the call will never * time out. * * \return Returns `true` if the reply has finished (with or without an error), `false` if * the wait has timed out. */ Task waitForFinished(std::chrono::milliseconds timeout = std::chrono::milliseconds{-1}); private: Task> waitForReadyReadImpl(std::chrono::milliseconds timeout) override; Task> waitForBytesWrittenImpl(std::chrono::milliseconds timeout) override; }; } // namespace QCoro::detail //! Returns a coroutine-friendly wrapper for QNetworkReply object. /*! * Returns a wrapper for the QNetworkReply \c s that provides coroutine-friendly * way to co_await read and write operations. * * @see docs/reference/qnetworkreply.md */ inline auto qCoro(QNetworkReply &s) noexcept { return QCoro::detail::QCoroNetworkReply{&s}; } //! \copydoc qCoro(QAbstractSocket &s) noexcept inline auto qCoro(QNetworkReply *s) noexcept { return QCoro::detail::QCoroNetworkReply{s}; } /*! \cond internal */ namespace QCoro::detail { template<> struct awaiter_type { using type = QCoroNetworkReply::WaitForFinishedOperation; }; } // namespace QCoro::detail qcoro-0.12.0/qcoro/network/qcorotcpserver.cpp000066400000000000000000000033071477357142500213400ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcorotcpserver.h" #include "qcorosignal.h" #include using namespace QCoro::detail; QCoroTcpServer::WaitForNewConnectionOperation::WaitForNewConnectionOperation(QTcpServer *server, int timeout_msecs) : WaitOperationBase(server, timeout_msecs) {} bool QCoroTcpServer::WaitForNewConnectionOperation::await_ready() const noexcept { return !mObj || mObj->hasPendingConnections(); } void QCoroTcpServer::WaitForNewConnectionOperation::await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept { mConn = QObject::connect(mObj, &QTcpServer::newConnection, std::bind(&WaitForNewConnectionOperation::resume, this, awaitingCoroutine)); startTimeoutTimer(awaitingCoroutine); } QTcpSocket *QCoroTcpServer::WaitForNewConnectionOperation::await_resume() { return mTimedOut ? nullptr : mObj->nextPendingConnection(); } QCoroTcpServer::QCoroTcpServer(QTcpServer *server) : mServer(server) {} QCoro::Task QCoroTcpServer::waitForNewConnection(int timeout_msecs) { return waitForNewConnection(std::chrono::milliseconds(timeout_msecs)); } QCoro::Task QCoroTcpServer::waitForNewConnection(std::chrono::milliseconds timeout) { const auto server = mServer; if (!server->isListening()) { co_return nullptr; } if (server->hasPendingConnections()) { co_return server->nextPendingConnection(); } const auto result = co_await qCoro(server.data(), &QTcpServer::newConnection, timeout); if (result.has_value()) { co_return server->nextPendingConnection(); } co_return nullptr; } qcoro-0.12.0/qcoro/network/qcorotcpserver.h000066400000000000000000000053271477357142500210110ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include "qcorotask.h" #include "waitoperationbase_p.h" #include "qcoronetwork_export.h" #include #include class QTcpServer; class QTcpSocket; namespace QCoro::detail { using namespace std::chrono_literals; //! QTcpServer wrapper with co_awaitable-friendly API. class QCORONETWORK_EXPORT QCoroTcpServer { //! An Awaitable that suspends the coroutine until new connection is available class WaitForNewConnectionOperation final : public WaitOperationBase { public: WaitForNewConnectionOperation(QTcpServer *server, int timeout_msecs = 30'000); bool await_ready() const noexcept; void await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept; QTcpSocket *await_resume(); }; public: ///! Constructor. explicit QCoroTcpServer(QTcpServer *server); /** * \brief co_awaitable equivalent to [`QTcpServer::waitForNewConnection()`][qtdoc-qtcpserver-waitForNewConnection]. * * Waits for at most \c timeout_msecs milliseconds. If the timeout is -1, the call will never time out. * * \return Returns \c QTcpSocket of the pending connection. Returns `nullptr` if the server is not in * the listening state or if the call times out. * * [qtdoc-qtcpserver-waitForNewConnection]: https://doc.qt.io/qt-5/qtcpserver.html#waitForNewConnection */ Task waitForNewConnection(int timeout_msecs = 30'000); //! Co_awaitable equivalent to [`QTcpServer::waitForNewConnection()`][qtdoc-qtcpserver-waitForNewConnection]. /*! * Unlike the Qt version, this overload uses `std::chrono::milliseconds` to express the * timeout rather than plain `int`. If the \c timeout is -1, the call will never time out. * * \return Returns \c QTcpSocket of the pending connection. Returns `nullptr` if the server is not in * the listening state or if the call times out. * * [qtdoc-qtcpserver-waitForNewConnection]: https://doc.qt.io/qt-5/qtcpserver.html#waitForNewConnection */ Task waitForNewConnection(std::chrono::milliseconds timeout); private: QPointer mServer; }; } // namespace QCoro::detail //! Returns a coroutine-friendly wrapper for QTcpServer object. /*! * Returns a wrapper for QTcpServer \c s that provides coroutine-friendly way * of co_awaiting new connections. * * @see docs/reference/qtcpserver.md */ inline auto qCoro(QTcpServer &s) noexcept { return QCoro::detail::QCoroTcpServer{&s}; } //! \copydoc qCoro(QTcpServer &s) noexcept inline auto qCoro(QTcpServer *s) noexcept { return QCoro::detail::QCoroTcpServer{s}; } qcoro-0.12.0/qcoro/qcoro.h000066400000000000000000000003151477357142500153520ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include "task.h" #include "qcorocore.h" #include "qcorodbus.h" #include "qcoronetwork.h" qcoro-0.12.0/qcoro/qcoroasyncgenerator.h000066400000000000000000000314531477357142500203260ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Daniel Vrátil // // SPDX-License-Identifier: MIT // // Based on cppcoro::async_generator (c) Lewis Baker, published under MIT license // https://github.com/lewissbaker/cppcoro/blob/master/include/cppcoro/async_generator.hpp #pragma once #include "coroutine.h" #include #include namespace QCoro { template class AsyncGenerator; namespace detail { template class AsyncGeneratorIterator; class AsyncGeneratorYieldOperation; class AsyncGeneratorAdvanceOperation; class AsyncGeneratorPromiseBase { public: AsyncGeneratorPromiseBase() noexcept = default; AsyncGeneratorPromiseBase(const AsyncGeneratorPromiseBase &) = delete; AsyncGeneratorPromiseBase(AsyncGeneratorPromiseBase &&) noexcept = default; AsyncGeneratorPromiseBase& operator=(const AsyncGeneratorPromiseBase &) = delete; AsyncGeneratorPromiseBase& operator=(AsyncGeneratorPromiseBase &&) noexcept = default; ~AsyncGeneratorPromiseBase() = default; std::suspend_always initial_suspend() const noexcept { return {}; } AsyncGeneratorYieldOperation final_suspend() noexcept; void unhandled_exception() noexcept { m_exception = std::current_exception(); } void return_void() noexcept {} /// Query if the generator has reached the end of the sequence. /// /// Only valid to call after resuming from an awaited advance operation. /// i.e. Either a begin() or iterator::operator++() operation. bool finished() const noexcept { return m_currentValue == nullptr; } void rethrow_if_unhandled_exception() { if (m_exception) { std::rethrow_exception(std::move(m_exception)); } } protected: AsyncGeneratorYieldOperation internal_yield_value() noexcept; private: friend class AsyncGeneratorYieldOperation; friend class AsyncGeneratorAdvanceOperation; friend class IteratorAwaitableBase; std::exception_ptr m_exception = nullptr; std::coroutine_handle<> m_consumerCoroutine; protected: void *m_currentValue = nullptr; }; class AsyncGeneratorYieldOperation final { public: explicit AsyncGeneratorYieldOperation(std::coroutine_handle<> consumer) noexcept : m_consumer(consumer) {} bool await_ready() const noexcept { return false; } std::coroutine_handle<> await_suspend([[maybe_unused]] std::coroutine_handle<> producer) noexcept { return m_consumer; } void await_resume() noexcept {} private: std::coroutine_handle<> m_consumer; }; inline AsyncGeneratorYieldOperation AsyncGeneratorPromiseBase::final_suspend() noexcept { m_currentValue = nullptr; return internal_yield_value(); } inline AsyncGeneratorYieldOperation AsyncGeneratorPromiseBase::internal_yield_value() noexcept { return AsyncGeneratorYieldOperation{m_consumerCoroutine}; } class IteratorAwaitableBase { protected: explicit IteratorAwaitableBase(std::nullptr_t) noexcept {} IteratorAwaitableBase( AsyncGeneratorPromiseBase &promise, std::coroutine_handle<> producerCoroutine) noexcept : m_promise(std::addressof(promise)) , m_producerCoroutine(producerCoroutine) {} public: bool await_ready() const noexcept { return false; } std::coroutine_handle<> await_suspend(std::coroutine_handle<> consumerCoroutine) noexcept { m_promise->m_consumerCoroutine = consumerCoroutine; return m_producerCoroutine; } protected: AsyncGeneratorPromiseBase *m_promise = nullptr; std::coroutine_handle<> m_producerCoroutine = {nullptr}; }; template class AsyncGeneratorPromise final : public AsyncGeneratorPromiseBase { using value_type = std::remove_reference_t; public: AsyncGeneratorPromise() noexcept = default; AsyncGenerator get_return_object() noexcept; AsyncGeneratorYieldOperation yield_value(value_type &value) noexcept { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) m_currentValue = const_cast *>(std::addressof(value)); return internal_yield_value(); } AsyncGeneratorYieldOperation yield_value(value_type &&value) noexcept { return yield_value(value); } T &value() const noexcept { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) return *const_cast(static_cast(m_currentValue)); } }; /** * @brief Promise type for asynchronous generator coroutine. */ template class AsyncGeneratorPromise final : public AsyncGeneratorPromiseBase { public: AsyncGeneratorPromise() noexcept = default; AsyncGenerator get_return_object() noexcept; AsyncGeneratorYieldOperation yield_value(T &&value) noexcept { m_currentValue = std::addressof(value); return internal_yield_value(); } T &&value() const noexcept { return std::move(*static_cast(m_currentValue)); } }; } // namespace detail template class AsyncGeneratorIterator final { using promise_type = detail::AsyncGeneratorPromise; using handle_type = std::coroutine_handle; class IncrementIteratorAwaitable final : public detail::IteratorAwaitableBase { public: explicit IncrementIteratorAwaitable(AsyncGeneratorIterator &iterator) noexcept : detail::IteratorAwaitableBase(iterator.m_coroutine.promise(), iterator.m_coroutine), m_iterator(iterator) {} AsyncGeneratorIterator &await_resume() { if (m_promise->finished()) { m_iterator = AsyncGeneratorIterator{nullptr}; m_promise->rethrow_if_unhandled_exception(); } return m_iterator; } private: AsyncGeneratorIterator &m_iterator; }; public: using iterator_category = std::input_iterator_tag; // Not sure what type should be used for difference_type as we don't // allow calculating difference between two iterators. using difference_type = std::ptrdiff_t; using value_type = std::remove_reference_t; using reference = std::add_lvalue_reference_t; using pointer = std::add_pointer_t; explicit AsyncGeneratorIterator(std::nullptr_t) noexcept {} explicit AsyncGeneratorIterator(handle_type coroutine) noexcept : m_coroutine(coroutine) {} /** * @brief Asynchronously increments the iterator * * Resume the generator coroutine and asynchonously waits for it * to yield a value. Resolves to a new iterator with the new value. * * If the generator coroutine throws an exception, co_awaiting on the * returned awaitable will re-throw the exception and the returned iterator * will be invalid (past-the-end iterator). **/ auto operator++() noexcept { return IncrementIteratorAwaitable{*this}; } /** * Returns reference to the value yielded by the generator coroutine. */ reference operator *() const noexcept { return m_coroutine.promise().value(); } bool operator==(const AsyncGeneratorIterator &other) const noexcept { return m_coroutine == other.m_coroutine; } bool operator!=(const AsyncGeneratorIterator &other) const noexcept { return !(*this == other); } private: handle_type m_coroutine = {nullptr}; }; /** * @brief AsyncGenerator is a return type for a generator coroutine. * * Generator is a function that yields a value and suspends its execution * until another value is requested by the caller. The AsyncGenerator * allows the generator function to also contain `co_await`. * * The generator behaves like an iterable, so one can easilly iterate over * values produced by the generator, until the generator finishes. If the * AsyncGenerator object is destroyed while the generator coroutine hasn't * finished yet, the coroutine will be suspended and terminated. * * The generator function is resumed whenever the iterator is incremented. * Therefore, the increment operation (operator++()) must be co_awaited. * Retrieveing the generated value from the current iterator is a synchronous * operation. * * @code * // Asynchronously generates "count" random numbers * QCoro::AsyncGenerator randomValueGenerator(int count) { * for (int i = 0; i < count; ++i) { * co_yield co_await generateRandomNumber(); * } * } * * QCoro::Task randomSum(int count) { * int sum = 0; * QCORO_FOREACH(int rand, randomValueGenerator(10)) { * sum += rand; * } * return sum; * } * @endcode **/ template class [[nodiscard]] AsyncGenerator { public: using promise_type = detail::AsyncGeneratorPromise; using iterator = AsyncGeneratorIterator; /// Constructs an empty asynchronous generator AsyncGenerator() noexcept = default; /// Constructs AsyncGenerator for given promise. explicit AsyncGenerator(promise_type &promise) noexcept : m_coroutine(std::coroutine_handle::from_promise(promise)) {} /// Move constructor AsyncGenerator(AsyncGenerator &&other) noexcept : m_coroutine(other.m_coroutine) { other.m_coroutine = nullptr; } /// Destructor. ~AsyncGenerator() { if (m_coroutine) { m_coroutine.destroy(); } } /// Move-assignment operator. AsyncGenerator& operator=(AsyncGenerator &&other) noexcept { m_coroutine = other.m_coroutine; other.m_coroutine = nullptr; return *this; } /// Copy constructor AsyncGenerator(const AsyncGenerator &) = delete; /// Copy assignment operator AsyncGenerator& operator=(const AsyncGenerator &) = delete; /** * \brief Returns an iterator-like awaitable * * The returned object is an awaitable that must be co_awaited to obtain * an asynchronous iterator to iterate over results of the asychronous generator. * * The asynchronout iterator can be used like a regular iterator, except that incrementing * the iterator returns an awaitable that must be co_awaited. * * If the generator coroutine throws an exception, it will be rethrown here. */ auto begin() noexcept { class BeginIteratorAwaitable final : public detail::IteratorAwaitableBase { public: explicit BeginIteratorAwaitable(std::nullptr_t) noexcept : IteratorAwaitableBase(nullptr) {} explicit BeginIteratorAwaitable(std::coroutine_handle producerCoroutine) noexcept : IteratorAwaitableBase(producerCoroutine.promise(), producerCoroutine) {} bool await_ready() const noexcept { return m_promise == nullptr || IteratorAwaitableBase::await_ready(); } iterator await_resume() { if (m_promise == nullptr) { return iterator{nullptr}; } else if (m_promise->finished()) { m_promise->rethrow_if_unhandled_exception(); return iterator{nullptr}; } return iterator{ std::coroutine_handle::from_promise(*static_cast(m_promise)) }; } }; return m_coroutine ? BeginIteratorAwaitable{m_coroutine} : BeginIteratorAwaitable{nullptr}; } /// Returns an iterator representing the finished generator. auto end() noexcept { return iterator{nullptr}; } void swap(AsyncGenerator &other) noexcept { using std::swap; swap(m_coroutine, other.m_coroutine); } private: std::coroutine_handle m_coroutine = {nullptr}; }; template void swap(AsyncGenerator &arg1, AsyncGenerator &arg2) noexcept { arg1.swap(arg2); } namespace detail { template AsyncGenerator AsyncGeneratorPromise::get_return_object() noexcept { return AsyncGenerator{*this}; } } // namespace detail } // namespace QCoro /** * \brief Helper macro to asynchronously loop over values produced by the AsyncGenerator. * * The macro is identical to Q_FOREACH, except that it makes sure to `co_await` on each * iteration. It's identical to the following piece of code: * * @code * for (auto it = co_await generator.begin(), end = generator.end(); it != end; co_await ++it) { * var = *it; * ... * } * @endcode * * @param var The full declaration of the iteration variable (incl. type, cvref), e.g. "const QString &user" * @param generator The AsyncGenerator object. **/ #define QCORO_FOREACH(var, generator) \ if (auto && _container = (generator); false) {} else \ for (auto _begin = co_await _container.begin(), _end = _container.end(); \ _begin != _end; \ co_await ++_begin) \ if (var = *_begin; false) {} else // NOLINT(bugprone-macro-parentheses) qcoro-0.12.0/qcoro/qcorofwd.h000066400000000000000000000005571477357142500160630ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once namespace QCoro { template class Task; template class Generator; template class GeneratorIterator; template class AsyncGenerator; template class AsyncGeneratorIterator; } // namespace QCoro qcoro-0.12.0/qcoro/qcorogenerator.h000066400000000000000000000234721477357142500172720ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include #include #include "coroutine.h" #include namespace QCoro { template class Generator; namespace detail { /** * @brief Promise type for generator coroutine. * * GeneratorPromise is automatically constructed by the compiler * (from Generator::promise_type) inside the generator coroutine. * * The generator coroutine is suspended on start (it won't produce any value * until asked for). **/ template class GeneratorPromise { using value_type = std::remove_reference_t; public: /** * Constructs the Generator object returned from the generator coroutine * to the caller. **/ Generator get_return_object(); /** * Indicates that the generator should suspend on start and * only generate the first value when asked for. **/ std::suspend_always initial_suspend() { return {}; } /** * Indicates that the generator coroutine should suspend when * it reaches the end (or returns), rather then destroyed. * * The generator coroutine is destroyed only when the Generator * object is destroyed. **/ std::suspend_always final_suspend() noexcept { mValue = nullptr; return {}; } /** * Called automatically when the generator coroutine throws an exception. * * The thrown exception is stored in the promise and will be re-thrown in the * caller (see QCoro::detail::GeneratorIterator::operator*()). **/ void unhandled_exception() { mException = std::current_exception(); } /** * Stores the current value produced (`co_yield`ed) by the generator coroutine. * * The value is stored and in the promise and the generator coroutine * is suspended. **/ std::suspend_always yield_value(value_type &value) { mValue = std::addressof(value); return {}; } std::suspend_always yield_value(value_type &&value) { mValue = std::addressof(value); return {}; } /** * The generator coroutine itself must always be `void`. **/ void return_void() {} /** * Returns the exception stored in the promise type (if any). **/ std::exception_ptr exception() const { return mException; } /** * Returns the current value stored in the promise type. **/ value_type &value() { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) return *const_cast(static_cast(mValue)); } /** * Whether the generator coroutine has finished or not. **/ bool finished() const { return mValue == nullptr; } void rethrowIfException() { if (mException) { std::rethrow_exception(mException); } } /** * @brief Prevent use of `co_await` inside the generator coroutine * * Use `QCoro::AsyncGenerator` if you need to use `co_await` inside the generator coroutine. */ template std::suspend_never await_transform(U &&) = delete; private: const void *mValue = nullptr; std::exception_ptr mException; }; } // namespace detail /** * @brief Iterator to loop over values produced by the generator coroutine. * * Dereferencing the iterator will return a value produced by the generator coroutine. * If the generator coroutine has thrown an exception, dereferencing the iterator will * re-throw the exception. * * Incrementing the iterator will resume the generator coroutine, letting it run until * it yields next value, or finishes. If the generator coroutine finishes, the iterator * will become invalid and will be equal to Generator::end(). * * The iterator can only be obtained from Generator::begin() and Generator::end() * methods. */ template class GeneratorIterator { using promise_type = detail::GeneratorPromise; public: using iterator_category = std::input_iterator_tag; // Not sure what type should be used for difference_type as we don't // allow calculating difference between two iterators. using difference_type = std::ptrdiff_t; using value_type = std::remove_reference_t; using reference = std::add_lvalue_reference_t; using pointer = std::add_pointer_t; /** * @brief Resumes the generator coroutine until it yields new value or finishes. * * Returns an iterator holding the next value produced by the generator coroutine * or an invalid iterator, indicating the generator coroutine has finishes. * * If the generator coroutine throws an exception, it will be rethrown from here. **/ GeneratorIterator operator++() { if (!mGeneratorCoroutine) { return *this; } mGeneratorCoroutine.resume(); // generate next value auto &promise = mGeneratorCoroutine.promise(); if (promise.finished()) { mGeneratorCoroutine = nullptr; promise.rethrowIfException(); } return *this; } /** * @brief Returns value produced by the generator coroutine. **/ reference operator *() const noexcept { return mGeneratorCoroutine.promise().value(); } bool operator==(const GeneratorIterator &other) const noexcept { return mGeneratorCoroutine == other.mGeneratorCoroutine; } bool operator!=(const GeneratorIterator &other) const noexcept { return !(operator==(other)); } private: friend class QCoro::Generator; /** * @brief Constructs an invalid iterator. **/ explicit GeneratorIterator(std::nullptr_t) {} /** * @brief Constructs an iterator associated with the given generator coroutine. **/ explicit GeneratorIterator(std::coroutine_handle generatorCoroutine) : mGeneratorCoroutine(generatorCoroutine) {} std::coroutine_handle mGeneratorCoroutine{nullptr}; }; /** * @brief A coroutine generator. * * The Generator class is returned from the generator coroutine. It's similar * to QCoro::Task in the sense that it provides caller with an interface to * the generator coroutine. * * There is no standard interface for generators defined in the C++ standard. * Following the generators implemented in cppcoro (which is a big inspiration) * for QCoro), the Generator provides begin() method to obtain an iterator * representing the current value produced by the generator. Each incrementation * of the iterator resumes the generator and provides next value. The end() method * can be used to obtain a past-end iterator to detect when the generator coroutine * has finished (no more values will be produced), unless the generator is infinite. * * When the Generator object is destroyed, the associated generator coroutine is * also destroyed, even if it has not yet finished. All values allocated on stack of * the generator coroutine will be destroyed automatically. */ template class Generator { public: using promise_type = detail::GeneratorPromise; using iterator = GeneratorIterator; explicit Generator() = default; Generator(Generator &&other) noexcept { mGeneratorCoroutine = other.mGeneratorCoroutine; other.mGeneratorCoroutine = std::coroutine_handle::from_address(nullptr); } Generator(const Generator &) = delete; Generator &operator=(Generator &&other) noexcept { mGeneratorCoroutine = other.mGeneratorCoroutine; other.mGeneratorCoroutine = std::coroutine_handle::from_address(nullptr); return *this; }; Generator &operator=(const Generator &) = delete; /** * @brief Destroys this Generator object and the associated generator coroutine. * * All values allocated on the generator stack are destroyed automatically. */ ~Generator() { if (mGeneratorCoroutine.address() != nullptr) { mGeneratorCoroutine.destroy(); } } /** * @brief Returns iterator "pointing" to the first value produced by the generator. * * If the generator coroutine did not produce any value and finished immediatelly, * the returned iterator will be equal to end(). * * If the generator coroutine has thrown an exception if will be rethrown from here. **/ iterator begin() { mGeneratorCoroutine.resume(); // generate first value if (mGeneratorCoroutine.promise().finished()) { // did not yield anything mGeneratorCoroutine.promise().rethrowIfException(); return iterator{nullptr}; } return iterator{mGeneratorCoroutine}; } /** * @brief Returns iterator indicating the past-last value produced by the generator. * * Can be used to check whether the generator have produced another value or * whether it has finished. **/ iterator end() { return iterator{nullptr}; } private: friend QCoro::Generator QCoro::detail::GeneratorPromise::get_return_object(); /** * @brief Constructs a new Generator object for given generator coroutine. * * This is called from GeneratorPromise::get_return_object(), which is invoked * automatically by C++ when starting the generator coroutine. */ explicit Generator(std::coroutine_handle generatorCoroutine) : mGeneratorCoroutine(generatorCoroutine) {} std::coroutine_handle mGeneratorCoroutine; }; } // namespace QCoro template QCoro::Generator QCoro::detail::GeneratorPromise::get_return_object() { using handle_type = std::coroutine_handle::promise_type>; return QCoro::Generator(handle_type::from_promise(*this)); } qcoro-0.12.0/qcoro/qcorolazytask.h000066400000000000000000000021171477357142500171370ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include "qcorotask.h" namespace QCoro { template class LazyTask; /*! \cond internal */ namespace detail { //! Specialization of QCoro::detail::isTask for LazyTask. template struct isTask> : std::true_type { using return_type = typename QCoro::LazyTask::value_type; }; template class LazyTaskPromise : public TaskPromise { public: using TaskPromise::TaskPromise; LazyTask get_return_object() noexcept; std::suspend_always initial_suspend() const noexcept; }; } // namespace detail /*! \endcond */ template class LazyTask final : public detail::TaskBase> { public: using promise_type = detail::LazyTaskPromise; using value_type = T; using detail::TaskBase::TaskBase; ~LazyTask() override; auto operator co_await() const noexcept; }; } // namespace QCoro #include "impl/lazytask.h" qcoro-0.12.0/qcoro/qcorotask.h000066400000000000000000000552131477357142500162440ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include "coroutine.h" #include "concepts_p.h" #include #include #include #include #include #include namespace QCoro { template class Task; /*! \cond internal */ namespace detail { template struct awaiter_type; template using awaiter_type_t = typename awaiter_type::type; //! Continuation that resumes a coroutine co_awaiting on currently finished coroutine. class TaskFinalSuspend { public: //! Constructs the awaitable, passing it a handle to the co_awaiting coroutine /*! * \param[in] awaitingCoroutine handle of the coroutine that is co_awaiting the current * coroutine (continuation). */ explicit TaskFinalSuspend(const std::vector> &awaitingCoroutines); //! Returns whether the just finishing coroutine should do final suspend or not /*! * If the coroutine is not being co_awaited by another coroutine, then don't * suspend and let the code reach the end of the coroutine, which will take * care of cleaning everything up. Otherwise we suspend and let the awaiting * coroutine to clean up for us. */ bool await_ready() const noexcept; //! Called by the compiler when the just-finished coroutine is suspended. for the very last time. /*! * It is given handle of the coroutine that is being co_awaited (that is the current * coroutine). If there is a co_awaiting coroutine and it has not been resumed yet, * it resumes it. * * Finally, it destroys the just finished coroutine and frees all allocated resources. * * \param[in] finishedCoroutine handle of the just finished coroutine */ template void await_suspend(std::coroutine_handle finishedCoroutine) noexcept; //! Called by the compiler when the just-finished coroutine should be resumed. /*! * In reality this should never be called, as our coroutine is done, so it won't be resumed. * In any case, this method does nothing. * */ constexpr void await_resume() const noexcept; private: std::vector> mAwaitingCoroutines; }; //! Base class for the \c Task promise_type. /*! * This is a promise_type for a Task returned from a coroutine. When a coroutine * is constructed, it looks at its return type, which will be \c Task, and constructs * new object of type \c Task::promise_type (which will be \c TaskPromise). Using * \c TaskPromise::get_return_object() it obtains a new object of Task. Then the * coroutine user code is executed and runs until the it reaches a suspend point - either * a co_await keyword, co_return or until it reaches the end of user code. * * You can think about promise as an interface that is callee-facing, while Task is * an caller-facing interface (in respect to the current coroutine). * * Promise interface must provide several methods: * * get_return_object() - it is called by the compiler at the very beginning of a coroutine * and is used to obtain the object that will be returned from the coroutine whenever it is * suspended. * * initial_suspend() - it is co_awaited by the code generated immediately before user * code. Depending on the Awaitable that it returns, the coroutine will either suspend * and the user code will only be executed once it is co_awaited by some other coroutine, * or it will begin executing the user code immediately. In case of QCoro, the promise always * returns std::suspend_never, which is a standard library awaitable, which prevents the * coroutine from being suspended at the beginning. * * final_suspend() - it is co_awaited when the coroutine co_returns, or when it reaches * the end of user code. Same as with initial_suspend(), depending on the type of Awaitable * it returns, it either suspends the coroutine (and then it must be destroyed explicitly * by the Awaiter), or resumes and takes care of destroying the frame pointer. In case of * QCoro, the promise returns a custom Awaitable called TaskFinalSuspend, which, when * co_awaited by the compiler-generated code will make sure that if there is a coroutine * co_awaiting on the current corutine, that the co_awaiting coroutine is resumed. * * unhandled_exception() - called by the compiler if the coroutine throws an unhandled * exception. The promise will store the exception and it will be rethrown when the * co_awaiting coroutine tries to retrieve a result of the coroutine that has thrown. * * return_value() - called by the compiler to store co_returned result of the function. * It must only be present if the coroutine is not void. * * return_void() - called by the compiler when the coroutine co_returns or flows of the * end of user code. It must only be present if the coroutine return type is void. * * await_transform() - this one is optional and is used by co_awaits inside the coroutine. * It allows the promise to transform the co_awaited type to an Awaitable. */ class TaskPromiseBase { public: //! Called when the coroutine is started to decide whether it should be suspended or not. /*! * We want coroutines that return QCoro::Task to start automatically, because it will * likely be executed from Qt's event loop, which will not co_await it, but rather call * it as a regular function, therefore it returns `std::suspend_never` awaitable, which * indicates that the coroutine should not be suspended. * */ std::suspend_never initial_suspend() const noexcept; //! Called when the coroutine co_returns or reaches the end of user code. /*! * This decides what should happen when the coroutine is finished. */ auto final_suspend() const noexcept; //! Called by co_await to obtain an Awaitable for type \c T. /*! * When co_awaiting on a value of type \c T, the type \c T must an Awaitable. To allow * to co_await even on types that are not Awaitable (e.g. 3rd party types like QNetworkReply), * C++ allows promise_type to provide \c await_transform() function that can transform * the type \c T into an Awaitable. This is a very powerful mechanism in C++ coroutines. * * For types \c T for which there is no valid await_transform() overload, the C++ attempts * to use those types directly as Awaitables. This is a perfectly valid scenario in cases * when co_awaiting a type that implements the neccessary Awaitable interface. * * In our implementation, the await_transform() is overloaded only for Qt types for which * a specialiation of the \c QCoro::detail::awaiter_type template class exists. The * specialization returns type of the Awaiter for the given type \c T. */ template>> auto await_transform(T &&value); //! If the type T is already an awaitable (including Task or LazyTask), then just forward it as it is. template auto && await_transform(T &&awaitable); //! \copydoc template QCoro::TaskPromiseBase::await_transform(T &&) template auto &await_transform(T &awaitable); //! Called by \c TaskAwaiter when co_awaited. /*! * This function is called by a TaskAwaiter, e.g. an object obtain by co_await * when a value of Task is co_awaited (in other words, when a coroutine co_awaits on * another coroutine returning Task). * * \param awaitingCoroutine handle for the coroutine that is co_awaiting on a coroutine that * represented by this promise. When our coroutine finishes, it's * our job to resume the awaiting coroutine. */ void addAwaitingCoroutine(std::coroutine_handle<> awaitingCoroutine); bool hasAwaitingCoroutine() const; void derefCoroutine(); void refCoroutine(); void destroyCoroutine(); protected: explicit TaskPromiseBase(); private: friend class TaskFinalSuspend; //! Handle of the coroutine that is currently co_awaiting this Awaitable std::vector> mAwaitingCoroutines; //! Indicates whether we can destroy the coroutine handle std::atomic mRefCount{0}; }; //! The promise_type for Task /*! * See \ref TaskPromiseBase documentation for explanation about promise_type. */ template class TaskPromise: public TaskPromiseBase { public: explicit TaskPromise() = default; ~TaskPromise() = default; //! Constructs a Task for this promise. Task get_return_object() noexcept; //! Called by the compiler when user code throws an unhandled exception. /*! * When user code throws but doesn't catch, it is ultimately caught by the code generated by * the compiler (effectively the entire user code is wrapped in try...catch ) and this method * is called. This method stores the exception. The exception is re-thrown when the calling * coroutine is resumed and tries to retrieve result of the finished coroutine that has thrown. */ void unhandled_exception(); //! Called form co_return statement to store result of the coroutine. /*! * \param[in] value the value returned by the coroutine. It is stored in the * promise, later can be retrieved by the calling coroutine. */ void return_value(T &&value) noexcept; //! \copydoc template TaskPromise::return_value(T &&value) noexcept void return_value(const T &value) noexcept; template requires QCoro::concepts::constructible_from void return_value(U &&value) noexcept; //! Retrieves the result of the coroutine. /*! * \return the value co_returned by the finished coroutine. If the coroutine has * thrown an exception, this method will instead rethrow the exception. */ T &result() &; //! \copydoc T &QCoro::TaskPromise::result() & T &&result() &&; private: //! Holds either the return value of the coroutine or exception thrown by the coroutine. std::variant mValue; }; //! Specialization of TaskPromise for coroutines returning \c void. template<> class TaskPromise: public TaskPromiseBase { public: // Constructor. explicit TaskPromise() = default; //! Destructor. ~TaskPromise() = default; //! \copydoc TaskPromise::get_return_object() Task get_return_object() noexcept; //! \copydoc TaskPromise::unhandled_exception() void unhandled_exception(); //! Promise type must have this function when the coroutine return type is void. void return_void() noexcept; //! Provides access to the result of the coroutine. /*! * Since this is a promise type for a void coroutine, the only result that * this can return is re-throwing an exception thrown by the coroutine, if * there's any. */ void result(); private: //! Exception thrown by the coroutine. std::exception_ptr mException; }; //! Base-class for Awaiter objects returned by the \c Task operator co_await(). template class TaskAwaiterBase { public: //! Returns whether to co_await bool await_ready() const noexcept; //! Called by co_await in a coroutine that co_awaits our awaited coroutine managed by the current task. /*! * In other words, let's have a case like this: * \code{.cpp} * Task<> doSomething() { * ... * co_return result; * }; * * Task<> getSomething() { * ... * const auto something = co_await doSomething(); * ... * } * \endcode * * If this Awaiter object is an awaiter of the doSomething() coroutine (e.g. has been constructed * by the co_await), then \c mAwaitedCoroutine is the handle of the doSomething() coroutine, * and \c awaitingCoroutine is a handle of the getSomething() coroutine which is awaiting the * completion of the doSomething() coroutine. * * This is implemented by passing the awaiting coroutine handle to the promise of the * awaited coroutine. When the awaited coroutine finishes, the promise will take care of * resuming the awaiting coroutine. At the same time this function resumes the awaited * coroutine. * * \param[in] awaitingCoroutine handle of the coroutine that is currently co_awaiting the * coroutine represented by this Tak. * \return returns whether the awaiting coroutine should be suspended, or whether the * co_awaited coroutine has finished synchronously and the co_awaiting coroutine doesn't * have to suspend. */ void await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept; protected: //! Constucts a new Awaiter object. /*! * \param[in] coroutine hande for the coroutine that is being co_awaited. */ explicit TaskAwaiterBase(std::coroutine_handle awaitedCoroutine); //! Handle of the coroutine that is being co_awaited by this awaiter std::coroutine_handle mAwaitedCoroutine = {}; }; template struct isTask : std::false_type { using return_type = T; }; template struct isTask> : std::true_type { using return_type = typename QCoro::Task::value_type; }; template constexpr bool isTask_v = isTask::value; template class TaskImpl, typename PromiseType> class TaskBase { public: explicit TaskBase() noexcept = default; explicit TaskBase(std::coroutine_handle coroutine); //! Task cannot be copy-constructed. TaskBase(const TaskBase &) = delete; //! Task cannot be copy-assigned. TaskBase &operator=(const TaskBase &) = delete; //! The task can be move-constructed. TaskBase(TaskBase &&otbher) noexcept; //! The task can be move-assigned. TaskBase &operator=(TaskBase &&other) noexcept; //! Destructor. virtual ~TaskBase(); //! Returns whether the task has finished. /*! * A task that is ready (represents a finished coroutine) must not attempt * to suspend the coroutine again. */ bool isReady() const; //! Provides an Awaiter for the coroutine machinery. /*! * The coroutine machinery looks for a suitable operator co_await overload * for the current Awaitable (this Task). It calls it to obtain an Awaiter * object, that is an object that the co_await keyword uses to suspend and * resume the coroutine. */ auto operator co_await() const noexcept; //! A callback to be invoked when the asynchronous task finishes. /*! * In some scenarios it is not possible to co_await a coroutine (for example from * a third-party code that cannot be changed to be a coroutine). In that case, * chaining a then() callback is a possible solution how to handle a result * of a coroutine without co_awaiting it. * * @param callback A function or a function object that can be invoked without arguments * (discarding the result of the coroutine) or with a single argument of type T that is * type matching the return type of the coroutine or is implicitly constructible from * the return type returned by the coroutine. * * @return Returns Task where R is the return type of the callback, so that the * result of the then() action can be co_awaited, if desired. If the callback * returns an awaitable (Task) then the result of then is the awaitable. */ template requires (std::is_invocable_v || (!std::is_void_v && std::is_invocable_v)) auto then(ThenCallback &&callback) &; template requires (std::is_invocable_v || (!std::is_void_v && std::is_invocable_v)) auto then(ThenCallback &&callback) &&; template requires ((std::is_invocable_v || (!std::is_void_v && std::is_invocable_v)) && std::is_invocable_v) auto then(ThenCallback &&callback, ErrorCallback &&errorCallback) &; template requires ((std::is_invocable_v || (!std::is_void_v && std::is_invocable_v)) && std::is_invocable_v) auto then(ThenCallback &&callback, ErrorCallback &&errorCallback) &&; private: template static auto invokeCb(ThenCallback &&callback, [[maybe_unused]] Args && ... args); template struct cb_invoke_result: std::conditional_t< std::is_invocable_v, std::invoke_result, std::invoke_result > {}; template struct cb_invoke_result: std::invoke_result {}; template using cb_invoke_result_t = typename cb_invoke_result::type; template::return_type> static auto handleException(ErrorCallback &errCb, const std::exception &exception) -> U; template> static auto thenImpl(TaskT task, ThenCallback &&thenCallback, ErrorCallback &&errorCallback) -> std::conditional_t, R, TaskImpl>; template> static auto thenImplRef(TaskT &task, ThenCallback &&thenCallback, ErrorCallback &&errorCallback) -> std::conditional_t, R, TaskImpl>; protected: std::coroutine_handle mCoroutine = {}; }; } // namespace detail /*! \endcond */ //! An asynchronously executed task. /*! * When a coroutine is called which has return type Task, the coroutine will * construct a new instance of Task which will be returned to the caller when * the coroutine is suspended - that is either when it co_awaits another coroutine * of finishes executing user code. * * In the sense of the interface that the task implements, it is an Awaitable. * * Task is constructed by a code generated at the beginning of the coroutine by * the compiler (i.e. before the user code). The code first creates a new frame * pointer, which internally holds the promise. The promise is of type \c R::promise_type, * where \c R is the return type of the function (so \c Task, in our case. * * One can think about it as Task being the caller-facing interface and promise being * the callee-facing interface. */ template class Task final : public detail::TaskBase> { public: //! Promise type of the coroutine. This is required by the C++ standard. using promise_type = detail::TaskPromise; //! The type of the coroutine return value. using value_type = T; using detail::TaskBase>::TaskBase; }; namespace detail { template concept TaskConvertible = requires(T val, TaskPromiseBase promise) { { promise.await_transform(val) }; }; template struct awaitable_return_type { using type = std::decay_t().await_resume())>; }; template struct awaitable_return_type { using type = std::decay_t().operator co_await())>::type>; }; template struct awaitable_return_type { using type = std::decay_t()))>::type>; }; template using awaitable_return_type_t = typename detail::awaitable_return_type::type; template requires TaskConvertible using convertible_awaitable_return_type_t = typename detail::awaitable_return_type().await_transform(Awaitable()))>::type; } // namespace detail //! Waits for a coroutine to complete in a blocking manner. /*! * Sometimes you may need to wait for a coroutine to finish without co_awaiting it - that is, * you want to wait for the coroutine in a blocking mode. This function does exactly that. * The function creates a nested QEventLoop and executes it until the coroutine has finished. * * \param task Coroutine to blockingly wait for. * \returns Result of the coroutine. */ template inline T waitFor(QCoro::Task &task); // \overload template inline T waitFor(QCoro::Task &&task); // \overload template inline auto waitFor(Awaitable &&awaitable); //! Connect a callback to be called when the asynchronous task finishes. /*! * Allows to register a callback to be called only when the context object * still exists when the task finishes. * * In contrast to the then function, the result of connect can not be co_awaited. * * @param context A QObject that still needs to exist when the task finishes in order for the callback to be invoked. * @param func The function that will be called when the task finishes. * For void tasks, it needs to have no arguments. * For all other types, it takes a value of the type as single argument. */ template requires std::is_invocable_v || std::is_invocable_v || std::is_invocable_v || std::is_invocable_v void connect(QCoro::Task &&task, QObjectSubclass *context, Callback func); template requires detail::TaskConvertible && (std::is_invocable_v || std::is_invocable_v> || std::is_invocable_v || std::is_invocable_v>) && (!detail::isTask_v) void connect(T &&future, QObjectSubclass *context, Callback func); } // namespace QCoro #include "impl/taskfinalsuspend.h" #include "impl/taskpromisebase.h" #include "impl/taskpromise.h" #include "impl/taskawaiterbase.h" #include "impl/taskbase.h" #include "impl/task.h" #include "impl/waitfor.h" #include "impl/connect.h" qcoro-0.12.0/qcoro/qml/000077500000000000000000000000001477357142500146505ustar00rootroot00000000000000qcoro-0.12.0/qcoro/qml/CMakeLists.txt000066400000000000000000000010441477357142500174070ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2021 Daniel Vrátil # SPDX-FileCopyrightText: 2022 Jonah Brüchert # # SPDX-License-Identifier: MIT add_qcoro_library( NAME Qml INCLUDEDIR Qml SOURCES qcoroqmltask.cpp qcoroqml.cpp CAMELCASE_HEADERS QCoroQmlTask QCoroQml QT_LINK_LIBRARIES PUBLIC Core Qml QCORO_LINK_LIBRARIES PUBLIC Coro ) if (${QT_VERSION_MAJOR} STREQUAL "6") target_link_libraries(QCoro${QT_VERSION_MAJOR}Qml PRIVATE Qt::QmlPrivate) endif() qcoro-0.12.0/qcoro/qml/qcoroqml.cpp000066400000000000000000000005101477357142500172050ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Jonah Brüchert // // SPDX-License-Identifier: MIT #include "qcoroqml.h" #include "qcoroqmltask.h" #include void QCoro::Qml::registerTypes() { qRegisterMetaType(); qmlRegisterAnonymousType("QCoro", 0); } qcoro-0.12.0/qcoro/qml/qcoroqml.h000066400000000000000000000003541477357142500166600ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Jonah Brüchert // // SPDX-License-Identifier: MIT #pragma once #include "qcoroqml_export.h" #include "qcoroqmltask.h" namespace QCoro::Qml { QCOROQML_EXPORT void registerTypes(); } qcoro-0.12.0/qcoro/qml/qcoroqmltask.cpp000066400000000000000000000047351477357142500201050ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Jonah Brüchert // // SPDX-License-Identifier: MIT #include "qcoroqmltask.h" #include #include #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QT_WARNING_PUSH QT_WARNING_DISABLE_MSVC(4458 4201) #include QT_WARNING_POP #endif Q_DECLARE_LOGGING_CATEGORY(qcoroqml) Q_LOGGING_CATEGORY(qcoroqml, "qcoro.qml") namespace QCoro { struct QmlTaskPrivate : public QSharedData { std::optional> task; QmlTaskPrivate() = default; QmlTaskPrivate(const QmlTaskPrivate &) : QSharedData() { Q_UNREACHABLE(); }; }; inline QJSEngine *getEngineForValue(const QJSValue &val) { #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // QJSValue::engine is deprecated, but still nicer, since it doesn't require using private symbols QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED return val.engine(); QT_WARNING_POP #else return QJSValuePrivate::engine(&val)->jsEngine(); #endif } QmlTask::QmlTask() noexcept : d(new QmlTaskPrivate()) { } QmlTask::QmlTask(QCoro::Task &&task) : d(new QmlTaskPrivate()) { d->task = std::move(task); } QmlTask &QmlTask::operator=(const QmlTask &other) = default; QmlTask::QmlTask(const QmlTask &other) = default; QmlTask::~QmlTask() = default; void QmlTask::then(QJSValue func) { if (!d->task) { qCWarning(qcoroqml, ".then called on a QmlTask that is not connected to any coroutine. " "Make sure you don't default-construct QmlTask in your code"); return; } if (!func.isCallable()) { qCWarning(qcoroqml, ".then called with an argument that is not a function. The .then call will do nothing"); return; } d->task->then([func = std::move(func)](const QVariant &result) mutable -> void { auto jsval = getEngineForValue(func)->toScriptValue(result); func.call({jsval}); }); } QmlTaskListener *QmlTask::await(const QVariant &intermediateValue) { QPointer listener = new QmlTaskListener(); if (!intermediateValue.isNull()) { listener->setValue(QVariant(intermediateValue)); } d->task->then([listener](auto &&value) { if (listener) { listener->setValue(std::move(value)); } }); return listener; } QVariant QmlTaskListener::value() const { return m_value; } void QmlTaskListener::setValue(QVariant &&value) { m_value = std::move(value); Q_EMIT valueChanged(); } } qcoro-0.12.0/qcoro/qml/qcoroqmltask.h000066400000000000000000000057201477357142500175450ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Jonah Brüchert // // SPDX-License-Identifier: MIT #pragma once #include #include #include #include "qcoroqml_export.h" #include namespace QCoro { struct QmlTaskPrivate; class QmlTaskListener; //! QML type that allows to react to asynchronous computations from QML struct QCOROQML_EXPORT QmlTask { Q_GADGET public: // Just for Q_DECLARE_METATYPE to be happy explicit QmlTask() noexcept; QmlTask(const QmlTask &other); QmlTask &operator=(const QmlTask &other); ~QmlTask(); //! Constructs a QmlTask from a QCoro::Task that resolves to a QVariant /*! * \param[in] task to await */ QmlTask(QCoro::Task &&task); //! Constructs a QmlTask from a QCoro::Task that resolves to an arbitrary type /*! * \param[in] task to await */ template QmlTask(QCoro::Task &&task) : QmlTask( task.then([](T &&result) -> QCoro::Task { co_return QVariant::fromValue(std::forward(result)); })) { // To rely on Qt's assertion to check whether the type is a registered metatype qMetaTypeId(); } //! Constructs a QmlTask from any awaitable type supported by QCoro. /*! * \param[in] object to await */ template requires (detail::TaskConvertible && !std::is_same_v) QmlTask(T &&future) : QmlTask(detail::toTask(std::forward(future))) { } //! Constructs a QmlTask from a QCoro::Task that doesn't return a value /*! * \param[in] task to await */ template QmlTask(QCoro::Task<> &&task) : QmlTask( task.then([]() -> QCoro::Task { co_return QVariant(); })) { } //! QML function that executes a given JavaScript function once the computation is finished /*! * \param[in] JavaScript function to call once the result is ready. * The result will be passed as first argument to the given function */ Q_INVOKABLE void then(QJSValue func); //! Allows to use tasks in property bindings /*! * The value property is initially null, and will be updated to the actual value once it is available. * Example usage: * ``` * text: asyncLoadText().await().value * ``` * * Optionally, an intermediate value can be passed to await, * which will be returned by value while calculating the asynchronous result. */ Q_INVOKABLE QCoro::QmlTaskListener *await(const QVariant &intermediateValue = {}); private: QSharedDataPointer d; }; class QmlTaskListener : public QObject { Q_OBJECT Q_PROPERTY(QVariant value READ value NOTIFY valueChanged) public: QVariant value() const; void setValue(QVariant &&value); Q_SIGNAL void valueChanged(); private: QVariant m_value; }; } Q_DECLARE_METATYPE(QCoro::QmlTask) qcoro-0.12.0/qcoro/quick/000077500000000000000000000000001477357142500151735ustar00rootroot00000000000000qcoro-0.12.0/qcoro/quick/CMakeLists.txt000066400000000000000000000006031477357142500177320ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2022 Daniel Vrátil # SPDX-FileCopyrightText: 2022 Jonah Brüchert # # SPDX-License-Identifier: MIT add_qcoro_library( NAME Quick SOURCES qcoroimageprovider.cpp CAMELCASE_HEADERS QCoroImageProvider QCORO_LINK_LIBRARIES PUBLIC Coro Core QT_LINK_LIBRARIES PUBLIC Core Gui Quick ) qcoro-0.12.0/qcoro/quick/qcoroimageprovider.cpp000066400000000000000000000020171477357142500216000ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Jonah Brüchert // // SPDX-License-Identifier: MIT #include "qcoroimageprovider.h" namespace QCoro { // Internal implementation of the QQuickImageResponse interface class QCoroImageResponse : public QQuickImageResponse { public: QQuickTextureFactory *textureFactory() const override; void reportFinished(QImage &&image); private: QImage m_image; }; ImageProvider::ImageProvider() = default; QQuickImageResponse *ImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize) { auto task = asyncRequestImage(id, requestedSize); auto *response = new QCoroImageResponse(); task.then([response](QImage &&image) { response->reportFinished(std::move(image)); }); return response; } QQuickTextureFactory *QCoroImageResponse::textureFactory() const { return QQuickTextureFactory::textureFactoryForImage(m_image); } void QCoroImageResponse::reportFinished(QImage &&image) { m_image = image; Q_EMIT finished(); } } qcoro-0.12.0/qcoro/quick/qcoroimageprovider.h000066400000000000000000000012501477357142500212430ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Jonah Brüchert // // SPDX-License-Identifier: MIT #pragma once #include #include #include "qcoroquick_export.h" namespace QCoro { //! Base class for coroutines based image providers class QCOROQUICK_EXPORT ImageProvider : public QQuickAsyncImageProvider { public: explicit ImageProvider(); //! This function needs to be re-implemented in a subclass. virtual QCoro::Task asyncRequestImage(const QString &id, const QSize &requestedSize) = 0; private: QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override; }; } qcoro-0.12.0/qcoro/task.h000066400000000000000000000015541477357142500151770ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #ifndef QCORO_NO_WARN_DEPRECATED_TASK_H #if defined(__GNUC__) || defined(__clang__) #pragma message "The qcoro/task.h include header is deprecated and will be removed " \ "in some future version of QCoro. Please use qcorotask.h header instead. " \ "You can define QCORO_NO_WARN_DEPRECATED_TASK_H to suppress this warning." #elif defined(_MSC_VER) #pragma message( "The qcoro/task.h include header is deprecated and will be removed " \ "in some future version of QCoro. Please use qcorotask.h header instead. " \ "You can define QCORO_NO_WARN_DEPRECATED_TASK_H to suppress this warning." ) #endif #endif #include "qcorotask.h" qcoro-0.12.0/qcoro/test/000077500000000000000000000000001477357142500150365ustar00rootroot00000000000000qcoro-0.12.0/qcoro/test/CMakeLists.txt000066400000000000000000000003511477357142500175750ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2023 Daniel Vrátil # # SPDX-License-Identifier: MIT add_qcoro_library( NAME Test INTERFACE CAMELCASE_HEADERS QCoroTest QT_LINK_LIBRARIES INTERFACE Test ) qcoro-0.12.0/qcoro/test/qcorotest.h000066400000000000000000000204331477357142500172340ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Daniel Vrátil // // SPDX-License-Identifier: MIT /* * This code is very closely based on qtestcase.h header file from Qt * Copyright (c) 2016 The Qt Company Ltd., LGPLv3/GPLv2 or GPLv3 or any * later version approved by the KDE Free Qt Foundation. */ #pragma once #include #include /** * @brief Coroutine-friendly version of QVERIFY test macro. **/ #define QCORO_VERIFY(statement) \ do { \ if (!QTest::qVerify(static_cast(statement), #statement, "", __FILE__, __LINE__)) \ co_return; \ } while (false) /** * @brief Coroutine-friendly version of QFAIL test macro. **/ #define QCORO_FAIL(message) \ do { \ QTest::qFail(static_cast(message), __FILE__, __LINE__); \ co_return; \ } while (false) /** * @brief Coroutine-friendly version of QVERIFY2 test macro. **/ #define QCORO_VERIFY2(statement, description) \ do { \ if (statement) { \ if (!QTest::qVerify(true, #statement, static_cast(description), __FILE__, __LINE__)) \ co_return; \ } else { \ if (!QTest::qVerify(false, #statement, static_cast(description), __FILE__, __LINE__)) \ co_return; \ } \ } while (false) /** * @brief Coroutine-friendly version of QCOMPARE test macro. **/ #define QCORO_COMPARE(actual, expected) \ do { \ if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__)) \ co_return; \ } while (false) #if !defined(QT_NO_EXCEPTIONS) /** * @brief Coroutine-friendly version of QVERIFY_THROWS_EXCEPTION macro. **/ #define QCORO_VERIFY_THROWS_EXCEPTION(exceptionType, ...) \ do { \ try { \ try { \ __VA_ARGS__; \ QTest::qFail("Expected exception of type " #exceptionType " to be thrown" \ " but no exception caught", \ __FILE__, __LINE__); \ co_return; \ } catch (const exceptionType &) {} \ } catch (const std::exception &e) { \ const QByteArray msg = QByteArray() + \ "Expected exception of type " #exceptionType \ " to be thrown, but std::exception caught with message " + \ e.what(); \ QTest::qFail(msg.constData(), __FILE__, __LINE__); \ co_return; \ } catch (...) { \ QTest::qFail("Expected exception of type " #exceptionType " to be thrown" \ " but unknown exception caught", \ __FILE__, __LINE__); \ co_return; \ } \ } while (false) /** * @brief Coroutine-friendly version of QVERIFY_EXCEPTION_THROWN macro. **/ #define QCORO_VERIFY_EXCEPTION_THROWN(expression, exceptionType) \ QCORO_VERIFY_THROWS_EXCEPTION(exceptionType, expression) #else // QT_NO_EXCEPTIONS #define QCORO_VERIFY_EXCEPTION_THROWN(expression, exceptionType) \ static_assert(false, "Support of exceptions is disabled") #define QCORO_VERIFY_THROWS_EXCEPTION(exceptionType, ...) \ static_assert(false, "Support for exceptions is disabled") #endif // QT_NO_EXCEPTIONS /* @cond INTERNAL */ #define QCORO_TRY_TIMEOUT_DEBUG_IMPL(expr, timeoutValue, step) \ if (!(expr)) { \ QTRY_LOOP_IMPL((expr), (2 * timeoutValue), step); \ if (expr) { \ QString msg = QString::fromUtf8("QTestLib: This test case check (\"%1\") failed because the requested timeout (%2 ms) was too short, %3 ms would have been sufficient this time."); \ msg = msg.arg(QString::fromUtf8(#expr)).arg(timeoutValue).arg(timeoutValue + qt_test_i); \ QCORO_FAIL(qPrintable(msg)); \ } \ } #define QCORO_TRY_IMPL(expr, timeout) \ const int qcoro_test_step = timeout < 350 ? timeout / 7 + 1 : 50; \ const int qcoro_test_timeoutValue = timeout; \ { QCORO_TRY_LOOP_IMPL((expr), qcoro_test_timeoutValue, qcoro_test_step); } \ QCORO_TRY_TIMEOUT_DEBUG_IMPL((expr), qcoro_test_timeoutValue, qcoro_test_step) \ /* @endcond */ /** * @brief Coroutine-friendly version of QVERIFY_WITH_TIMEOUT test macro. **/ #define QCORO_TRY_VERIFY_WITH_TIMEOUT(expr, timeout) \ do { \ QCORO_TRY_IMPL((expr), timeout); \ QCORO_VERIFY(expr); \ } while (false); /** * @brief Coroutine-friendly version of QTRY_VERIFY test macro. **/ #define QCORO_TRY_VERIFY(expr) QCORO_TRY_VERIFY_WITH_TIMEOUT((expr), 5000) /** * @brief Coroutine-friendly version of QTRY_VERIFY2_WITH_TIMEOUT test macro. **/ #define QCORO_TRY_VERIFY2_WITH_TIMEOUT(expr, messageExpression, timeout) \ do { \ QCORO_TRY_IMPL((expr), timeout); \ QCORO_VERIFY2((expr), messageExpression); \ } while (false) /** * @brief Coroutine-friendly version of QTRY_VERIFY2 test macro. **/ #define QCORO_TRY_VERIFY2(expr, messageExpression) \ QCORO_TRY_VERIFY2_WITH_TIMEOUT((expr), (messageExpression), 5000) /** * @brief Coroutine-friendly version of QTRY_COMPARE_WITH_TIMEOUT test macro. **/ #define QCORO_TRY_COMPARE_WITH_TIMEOUT(expr, expected, timeout) \ do { \ QCORO_TRY_IMPL(((expr) == (expected)), timeout); \ QCORO_COMPARE((expr), expected); \ } while (false) /** * @brief Coroutine-friendly version of QTRY_COMPARE test macro. **/ #define QCORO_TRY_COMPARE(expr, expected) \ QCORO_TRY_COMPARE_WITH_TIMEOUT((expr), (expected), 5000) /** * @internal **/ #define QCORO_SKIP_INTERNAL(statement) \ do { \ QTest::qSkip(static_cast(statement), __FILE__, __LINE__); \ co_return; \ } while (false) /** * @brief Coroutine-friendly version of QSKIP test macro. **/ #define QCORO_SKIP(statement, ...) QCORO_SKIP_INTERNAL(statement) /** * @brief Coroutine-friendly version of QEXPECT_FAIL test macro. **/ #define QCORO_EXPECT_FAIL(dataIndex, comment, mode) \ do { \ if (!QTest::qExpectFail(dataIndex, static_cast(comment), QTest::mode, __FILE__, __LINE__)) \ co_return; \ } while (false) /** * @brief Coroutine-friendly version of QTEST test macro. **/ #define QCORO_TEST(actual, testElement) \ do { \ if (!QTest::qTest(actual, testElement, #actual, #testElement, __FILE__, __LINE__)) \ co_return; \ } while (false) qcoro-0.12.0/qcoro/waitoperationbase_p.h000066400000000000000000000032061477357142500202700ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include "macros_p.h" #include "coroutine.h" #include #include namespace QCoro::detail { //! Base class for co_awaitable waitFor* operations. template class WaitOperationBase { public: Q_DISABLE_COPY(WaitOperationBase) QCORO_DEFAULT_MOVE(WaitOperationBase) ~WaitOperationBase() = default; bool await_resume() noexcept { return !mTimedOut; } protected: WaitOperationBase(T *obj, int timeout_msecs) : mObj{obj} { if (timeout_msecs > -1) { mTimeoutTimer = std::make_unique(); mTimeoutTimer->setInterval(timeout_msecs); mTimeoutTimer->setSingleShot(true); } } void startTimeoutTimer(std::coroutine_handle<> awaitingCoroutine) { if (!mTimeoutTimer) { return; } QObject::connect(mTimeoutTimer.get(), &QTimer::timeout, [this, awaitingCoroutine]() mutable { mTimedOut = true; resume(awaitingCoroutine); }); mTimeoutTimer->start(); } void resume(std::coroutine_handle<> awaitingCoroutine) { if (mTimeoutTimer) { mTimeoutTimer->stop(); } QObject::disconnect(mConn); QTimer::singleShot(0, [awaitingCoroutine]() mutable { awaitingCoroutine.resume(); }); } QPointer mObj; std::unique_ptr mTimeoutTimer; QMetaObject::Connection mConn; bool mTimedOut = false; }; } // namespace QCoro::detail qcoro-0.12.0/qcoro/websockets/000077500000000000000000000000001477357142500162305ustar00rootroot00000000000000qcoro-0.12.0/qcoro/websockets/CMakeLists.txt000066400000000000000000000006441477357142500207740ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2022 Daniel Vrátil # # SPDX-License-Identifier: MIT add_qcoro_library( NAME WebSockets SOURCES qcorowebsocket.cpp qcorowebsocketserver.cpp CAMELCASE_HEADERS QCoroWebSockets QCoroWebSocket QCoroWebSocketServer QCORO_LINK_LIBRARIES PUBLIC Coro Core QT_LINK_LIBRARIES PUBLIC Core Network WebSockets ) qcoro-0.12.0/qcoro/websockets/qcorowebsocket.cpp000066400000000000000000000165001477357142500217700ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcorowebsocket.h" #include "qcoroasyncgenerator.h" #include "qcorosignal.h" #include #include using namespace QCoro::detail; using TupleQInt64QByteArray = std::tuple; using TupleQByteArrayBool = std::tuple; using TupleQStringBool = std::tuple; Q_DECLARE_METATYPE(std::optional) Q_DECLARE_METATYPE(std::optional) Q_DECLARE_METATYPE(std::optional>) Q_DECLARE_METATYPE(std::optional) Q_DECLARE_METATYPE(std::optional>) namespace { class WebSocketStateWatcher : public QObject { Q_OBJECT public: WebSocketStateWatcher(QWebSocket *socket, QAbstractSocket::SocketState desiredState) : mState(connect(socket, &QWebSocket::stateChanged, this, [this, desiredState](auto newState) { if (newState == desiredState) { emitReady(true); } })) , mError(connect(socket, qOverload( #if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) &QWebSocket::errorOccurred #else &QWebSocket::error #endif ), this, [this](auto error) { qWarning() << "QWebSocket failed to connect to a websocket server: " << error; emitReady(false); })) {} Q_SIGNALS: void ready(bool result); private: void emitReady(bool result) { disconnect(mState); disconnect(mError); Q_EMIT ready(result); } QMetaObject::Connection mState; QMetaObject::Connection mError; }; template struct signal_args; template struct signal_args { using types = std::tuple ...>; }; template using signal_args_t = typename signal_args::types; template struct unwrapped_signal_args; template struct unwrapped_signal_args> { private: using args_tuple = std::tuple ...>; public: using type = std::conditional_t == 1, std::tuple_element_t<0, args_tuple>, args_tuple>; }; template using unwrapped_signal_args_t = typename unwrapped_signal_args::type; class WebSocketSignalWatcher : public QObject { Q_OBJECT public: template WebSocketSignalWatcher(QWebSocket *socket, Signal signal) { qRegisterMetaType>>(); qRegisterMetaType>>(); qRegisterMetaType>>(); qRegisterMetaType>>(); qRegisterMetaType>>(); connect(socket, signal, this, [this](auto && ... args) { Q_EMIT this->ready(std::make_optional(std::make_tuple(std::forward(args) ...))); }); connect(socket, &QWebSocket::stateChanged, this, [this](auto state) { // In theory, WebSocketSignalWatcher should never be used on // unconnected socket, so maybe the check is redundant if (state != QAbstractSocket::ConnectedState) { Q_EMIT this->ready(std::optional>{}); } }); } Q_SIGNALS: void ready(std::optional>); // ping void ready(std::optional>); // binary frames void ready(std::optional>); // binary messages void ready(std::optional>); // text frames void ready(std::optional>); // text messages }; template auto watcherGenerator(QWebSocket *ws, Signal signal, std::chrono::milliseconds timeout) -> QCoro::AsyncGenerator>> { WebSocketSignalWatcher watcher(ws, signal); using signalType = std::optional>; auto signalListener = qCoroSignalListener(&watcher, qOverload(&WebSocketSignalWatcher::ready), timeout); auto it = co_await signalListener.begin(); while (it != signalListener.end()) { if (!(*it).has_value()) { break; } // If the signal is a single-value tuple, we unwrap it from the tuple, otherwise we yield the whole tuple. if constexpr (std::tuple_size_v == 1) { co_yield std::get<0>(**it); } else { co_yield **it; } co_await ++it; } } } // namespace QCoroWebSocket::QCoroWebSocket(QWebSocket *socket) : mWebSocket(socket) {} QCoro::Task QCoroWebSocket::open(const QUrl &url, std::chrono::milliseconds timeout) { if (mWebSocket->state() == QAbstractSocket::ConnectedState) { co_return true; } WebSocketStateWatcher watcher(mWebSocket, QAbstractSocket::ConnectedState); mWebSocket->open(url); const auto result = co_await qCoro(&watcher, &WebSocketStateWatcher::ready, timeout); co_return result.value_or(false); } QCoro::Task QCoroWebSocket::open(const QNetworkRequest &request, std::chrono::milliseconds timeout) { if (mWebSocket->state() == QAbstractSocket::ConnectedState) { co_return true; } WebSocketStateWatcher watcher(mWebSocket, QAbstractSocket::ConnectedState); mWebSocket->open(request); const auto result = co_await qCoro(&watcher, &WebSocketStateWatcher::ready, timeout); co_return result.value_or(false); } QCoro::Task> QCoroWebSocket::ping(const QByteArray &payload, std::chrono::milliseconds timeout) { if (mWebSocket->state() != QAbstractSocket::ConnectedState) { co_return std::nullopt; } WebSocketSignalWatcher watcher(mWebSocket, &QWebSocket::pong); mWebSocket->ping(payload); const auto result = co_await qCoro(&watcher, qOverload>>(&WebSocketSignalWatcher::ready), timeout); if (result.has_value() && (*result).has_value()) { co_return std::chrono::milliseconds{std::get<0>(**result)}; } co_return std::nullopt; } QCoro::AsyncGenerator> QCoroWebSocket::binaryFrames( std::chrono::milliseconds timeout) { return watcherGenerator(mWebSocket, &QWebSocket::binaryFrameReceived, timeout); } QCoro::AsyncGenerator QCoroWebSocket::binaryMessages( std::chrono::milliseconds timeout) { return watcherGenerator(mWebSocket, &QWebSocket::binaryMessageReceived, timeout); } QCoro::AsyncGenerator> QCoroWebSocket::textFrames( std::chrono::milliseconds timeout) { return watcherGenerator(mWebSocket, &QWebSocket::textFrameReceived, timeout); } QCoro::AsyncGenerator QCoroWebSocket::textMessages( std::chrono::milliseconds timeout) { return watcherGenerator(mWebSocket, &QWebSocket::textMessageReceived, timeout); } #include "qcorowebsocket.moc" qcoro-0.12.0/qcoro/websockets/qcorowebsocket.h000066400000000000000000000031341477357142500214340ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include "qcorotask.h" #include "qcoroasyncgenerator.h" #include "qcorowebsockets_export.h" #include #include #include #include class QWebSocket; class QNetworkRequest; class QUrl; namespace QCoro::detail { class QCOROWEBSOCKETS_EXPORT QCoroWebSocket { public: explicit QCoroWebSocket(QWebSocket *websocket); Task open(const QUrl &url, std::chrono::milliseconds timeout = std::chrono::milliseconds{-1}); Task open(const QNetworkRequest &request, std::chrono::milliseconds timeout = std::chrono::milliseconds{-1}); Task> ping(const QByteArray &payload, std::chrono::milliseconds timeout = std::chrono::milliseconds{-1}); AsyncGenerator> binaryFrames(std::chrono::milliseconds timeout = std::chrono::milliseconds{-1}); AsyncGenerator binaryMessages(std::chrono::milliseconds timeout = std::chrono::milliseconds{-1}); AsyncGenerator> textFrames(std::chrono::milliseconds timeout = std::chrono::milliseconds{-1}); AsyncGenerator textMessages(std::chrono::milliseconds timeout = std::chrono::milliseconds{-1}); private: QWebSocket *mWebSocket; }; } // namespace QCoro::detail auto inline qCoro(QWebSocket *websocket) noexcept { return QCoro::detail::QCoroWebSocket{websocket}; } auto inline qCoro(QWebSocket &websocket) noexcept { return QCoro::detail::QCoroWebSocket{&websocket}; } qcoro-0.12.0/qcoro/websockets/qcorowebsockets.h000066400000000000000000000002611477357142500216150ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include "qcorowebsocket.h" #include "qcorowebsocketserver.h" qcoro-0.12.0/qcoro/websockets/qcorowebsocketserver.cpp000066400000000000000000000025541477357142500232230ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcorowebsocketserver.h" #include "qcorosignal.h" #include #include using namespace QCoro::detail; namespace { class QCoroWebSocketServerSignalListener : public QObject { Q_OBJECT public: QCoroWebSocketServerSignalListener(QWebSocketServer *server) { connect(server, &QWebSocketServer::closed, this, [this]() { Q_EMIT ready(nullptr); }); connect(server, &QWebSocketServer::newConnection, [this, server]() { Q_EMIT ready(server->nextPendingConnection()); }); } Q_SIGNALS: void ready(QWebSocket *socket); }; } // namespace QCoroWebSocketServer::QCoroWebSocketServer(QWebSocketServer *server) : mServer(server) {} QCoro::Task QCoroWebSocketServer::nextPendingConnection(std::chrono::milliseconds timeout) { auto * const server = mServer; if (!server->isListening()) { co_return nullptr; } if (server->hasPendingConnections()) { co_return server->nextPendingConnection(); } QCoroWebSocketServerSignalListener listener(server); const auto result = co_await qCoro(&listener, &QCoroWebSocketServerSignalListener::ready, timeout); co_return result.value_or(nullptr); } #include "qcorowebsocketserver.moc" qcoro-0.12.0/qcoro/websockets/qcorowebsocketserver.h000066400000000000000000000014441477357142500226650ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include "qcorotask.h" #include "qcorowebsockets_export.h" #include class QWebSocket; class QWebSocketServer; namespace QCoro::detail { class QCOROWEBSOCKETS_EXPORT QCoroWebSocketServer { public: explicit QCoroWebSocketServer(QWebSocketServer *server); Task nextPendingConnection(std::chrono::milliseconds timeout = std::chrono::milliseconds{-1}); private: QWebSocketServer *mServer; }; } // namespace QCoro::detail auto inline qCoro(QWebSocketServer *server) noexcept { return QCoro::detail::QCoroWebSocketServer{server}; } auto inline qCoro(QWebSocketServer &server) noexcept { return QCoro::detail::QCoroWebSocketServer{&server}; } qcoro-0.12.0/requirements.txt000066400000000000000000000003021477357142500162130ustar00rootroot00000000000000setuptools wheel pymdown-extensions~=10.11 pygments~=2.19 mkdocs mkdocs-material mkdocs-section-index mkdocs-include-markdown-plugin mkdocs-macros-plugin mkdocs-blogging-plugin mkdocs-rss-pluginqcoro-0.12.0/tests/000077500000000000000000000000001477357142500140765ustar00rootroot00000000000000qcoro-0.12.0/tests/CMakeLists.txt000066400000000000000000000104271477357142500166420ustar00rootroot00000000000000add_subdirectory(testlibs) include(CMakeParseArguments) function(_enable_supressions _name) function(_enable_supressions_file _filename) if (EXISTS "${_filename}") set_tests_properties(test-${_name} PROPERTIES ENVIRONMENT LSAN_OPTIONS=suppressions=${_filename} ) endif() endfunction() _enable_supressions_file("${CMAKE_CURRENT_SOURCE_DIR}/${_name}-qt${QT_VERSION_MAJOR}-lsan.supp") endfunction() function(qcoro_add_test _name) set(options SKIP_ADD_TEST) set(oneValueArgs) set(multiValueAgs LINK_LIBRARIES) cmake_parse_arguments(TEST "${options}" "${oneValueArgs}" "${multiValueAgs}" ${ARGN}) add_executable(test-${_name} ${_name}.cpp) target_link_libraries( test-${_name} PRIVATE qcoro_testlib QCoro${QT_VERSION_MAJOR}Core Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Test ${TEST_LINK_LIBRARIES} Threads::Threads ) set_target_defaults(test-${_name}) if (NOT TEST_SKIP_ADD_TEST) add_test(NAME test-${_name} COMMAND test-${_name}) _enable_supressions(${_name}) endif() target_code_coverage(test-${_name} AUTO ALL EXCLUDE "${CMAKE_BINARY_DIR}" "tests/testlibs/*") endfunction() function(qcoro_add_network_test _name) qcoro_add_test(${_name} ${ARGN}) target_link_libraries( test-${_name} PRIVATE QCoro${QT_VERSION_MAJOR}Network Qt${QT_VERSION_MAJOR}::Network ) if(NOT HAVE_CXX_ATOMICS_WITHOUT_LIB) target_link_libraries(test-${_name} PRIVATE atomic) endif() endfunction() function(qcoro_add_dbus_test _name) qcoro_add_test(${_name} ${ARGN} SKIP_ADD_TEST) add_dependencies(test-${_name} testdbusserver qcoro_test_dbus) target_link_libraries( test-${_name} PRIVATE QCoro${QT_VERSION_MAJOR}DBus qcoro_test_dbus ) target_compile_definitions(test-${_name} PRIVATE TESTDBUSSERVER_EXECUTABLE=\"$\") if (APPLE) # On MacOS dbus-launch doesn't work, so we rely on the session dbus running add_test(NAME test-${_name} COMMAND test-${_name}) else() add_test(NAME test-${_name} COMMAND dbus-launch $) endif() _enable_supressions(${_name}) endfunction() function(qcoro_add_websockets_test _name) qcoro_add_network_test(${_name} ${ARGN}) target_link_libraries( test-${_name} PRIVATE QCoro${QT_VERSION_MAJOR}WebSockets Qt${QT_VERSION_MAJOR}::WebSockets qcoro_test_ws ) endfunction() function(qcoro_add_qml_test _name) qcoro_add_network_test(${_name} ${ARGN}) target_link_libraries( test-${_name} PRIVATE QCoro${QT_VERSION_MAJOR}Qml Qt${QT_VERSION_MAJOR}::Qml ) endfunction() function(qcoro_add_quick_test _name) qcoro_add_test(${_name} ${ARGN}) target_link_libraries( test-${_name} PRIVATE QCoro${QT_VERSION_MAJOR}Quick Qt${QT_VERSION_MAJOR}::Quick Qt${QT_VERSION_MAJOR}::QuickPrivate ) get_test_property(test-${_name} ENVIRONMENT _env) list(APPEND _env QT_QPA_PLATFORM=offscreen) set_tests_properties(test-${_name} PROPERTIES ENVIRONMENT "${_env}") endfunction() qcoro_add_test(qtimer) qcoro_add_test(qcoroprocess) qcoro_add_test(qcorosignal) qcoro_add_test(qcorothread) qcoro_add_test(qcorotask) qcoro_add_test(qcorolazytask) qcoro_add_test(testconstraints) qcoro_add_test(qfuture LINK_LIBRARIES Qt${QT_VERSION_MAJOR}::Concurrent) qcoro_add_test(qcorogenerator) qcoro_add_test(qcoroasyncgenerator) qcoro_add_test(qcorowaitfor) if (QCORO_WITH_QTDBUS) qcoro_add_dbus_test(qdbuspendingcall) qcoro_add_dbus_test(qdbuspendingreply) endif() if (QCORO_WITH_QTNETWORK) qcoro_add_network_test(qcorolocalsocket) qcoro_add_network_test(qcoroabstractsocket) qcoro_add_network_test(qcoronetworkreply) qcoro_add_network_test(qcorotcpserver) # Tests for test utilities qcoro_add_network_test(testhttpserver) endif() if (QCORO_WITH_QTWEBSOCKETS) qcoro_add_websockets_test(qcorowebsocket) qcoro_add_websockets_test(qcorowebsocketserver) endif() if (QCORO_WITH_QML) qcoro_add_qml_test(qcoroqmltask) endif() if (QCORO_WITH_QTQUICK) qcoro_add_quick_test(qcoroimageprovider) endif() qcoro-0.12.0/tests/qcoroabstractsocket.cpp000066400000000000000000000270161477357142500206700ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "testhttpserver.h" #include "testobject.h" #include "qcoroiodevice_macros.h" #include "qcoro/network/qcoroabstractsocket.h" #include #include #include class QCoroAbstractSocketTest : public QCoro::TestObject { Q_OBJECT private: QCoro::Task<> testWaitForConnectedTriggers_coro(QCoro::TestContext) { QTcpSocket socket; QCORO_DELAY(socket.connectToHost(QHostAddress::LocalHost, mServer.port())); co_await qCoro(socket).waitForConnected(); QCORO_COMPARE(socket.state(), QAbstractSocket::ConnectedState); // Make sure the server gets the connection as well QCORO_VERIFY(mServer.waitForConnection()); } void testThenWaitForConnectedTriggers_coro(TestLoop &el) { QTcpSocket socket; QCORO_DELAY(socket.connectToHost(QHostAddress::LocalHost, mServer.port())); bool called = false; qCoro(socket).waitForConnected().then([&](bool connected) { called = true; el.quit(); QVERIFY(connected); }); el.exec(); QVERIFY(called); QVERIFY(mServer.waitForConnection()); } QCoro::Task<> testWaitForDisconnectedTriggers_coro(QCoro::TestContext) { QTcpSocket socket; co_await qCoro(socket).connectToHost(QHostAddress::LocalHost, mServer.port()); QCORO_COMPARE(socket.state(), QAbstractSocket::ConnectedState); QCORO_DELAY(socket.disconnectFromHost()); co_await qCoro(socket).waitForDisconnected(); QCORO_COMPARE(socket.state(), QAbstractSocket::UnconnectedState); QCORO_VERIFY(mServer.waitForConnection()); } void testThenWaitForDisconnectedTriggers_coro(TestLoop &el) { QTcpSocket socket; bool called = false; qCoro(socket).connectToHost(QHostAddress::LocalHost, mServer.port()).then([&](bool connected) { if (!connected) { el.quit(); } QVERIFY(connected); QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); QCORO_DELAY(socket.disconnectFromHost()); qCoro(socket).waitForDisconnected().then([&](bool connected) { called = true; el.quit(); QVERIFY(connected); }); }); el.exec(); QVERIFY(called); QVERIFY(mServer.waitForConnection()); } QCoro::Task<> testDoesntCoAwaitConnectedSocket_coro(QCoro::TestContext context) { QTcpSocket socket; co_await qCoro(socket).connectToHost(QHostAddress::LocalHost, mServer.port()); QCORO_COMPARE(socket.state(), QAbstractSocket::ConnectedState); context.setShouldNotSuspend(); co_await qCoro(socket).waitForConnected(); socket.write("GET / HTTP/1.1\r\n"); QCORO_VERIFY(mServer.waitForConnection()); } void testThenDoesntCoAwaitConnectedSocket_coro(TestLoop &el) { QTcpSocket socket; bool called = false; qCoro(socket).connectToHost(QHostAddress::LocalHost, mServer.port()).then([&](bool connected) { if (!connected) { el.quit(); } QVERIFY(connected); QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); qCoro(socket).waitForConnected().then([&](bool connected) { called = true; el.quit(); QVERIFY(connected); }); }); el.exec(); QVERIFY(called); QVERIFY(mServer.waitForConnection()); } QCoro::Task<> testDoesntCoAwaitDisconnectedSocket_coro(QCoro::TestContext context) { context.setShouldNotSuspend(); mServer.setExpectTimeout(true); // no-one actually connects, so the server times out. QTcpSocket socket; QCORO_COMPARE(socket.state(), QAbstractSocket::UnconnectedState); co_await qCoro(socket).waitForDisconnected(); } void testThenDoesntCoAwaitDisconnectedSocket_coro(TestLoop &el) { mServer.setExpectTimeout(true); // no-one actually connects, so the server time out. QTcpSocket socket; QCOMPARE(socket.state(), QAbstractSocket::UnconnectedState); bool called = false; qCoro(socket).waitForDisconnected().then([&](bool disconnected) { called = true; el.quit(); QVERIFY(!disconnected); }); el.exec(); QVERIFY(called); } QCoro::Task<> testConnectToServerWithArgs_coro(QCoro::TestContext) { QTcpSocket socket; co_await qCoro(socket).connectToHost(QHostAddress::LocalHost, mServer.port()); QCORO_COMPARE(socket.state(), QAbstractSocket::ConnectedState); QCORO_VERIFY(mServer.waitForConnection()); } void testThenConnectToServerWithArgs_coro(TestLoop &el) { QTcpSocket socket; bool called = false; qCoro(socket).connectToHost(QHostAddress::LocalHost, mServer.port()).then([&](bool connected) { called = true; el.quit(); QVERIFY(connected); QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); }); el.exec(); QVERIFY(called); QVERIFY(mServer.waitForConnection()); } QCoro::Task<> testWaitForConnectedTimeout_coro(QCoro::TestContext) { mServer.setExpectTimeout(true); QTcpSocket socket; QCORO_TEST_TIMEOUT(co_await qCoro(socket).waitForConnected(10ms)); } void testThenWaitForConnectedTimeout_coro(TestLoop &el) { mServer.setExpectTimeout(true); QTcpSocket socket; const auto start = std::chrono::steady_clock::now(); bool called = false; qCoro(socket).waitForConnected(10ms).then([&](bool connected) { called = true; el.quit(); QVERIFY(!connected); }); el.exec(); const auto end = std::chrono::steady_clock::now(); QVERIFY(end - start < 500ms); QVERIFY(called); } QCoro::Task<> testWaitForDisconnectedTimeout_coro(QCoro::TestContext) { mServer.setExpectTimeout(true); QTcpSocket socket; co_await qCoro(socket).connectToHost(QHostAddress::LocalHost, mServer.port()); QCORO_COMPARE(socket.state(), QAbstractSocket::ConnectedState); QCORO_TEST_TIMEOUT(co_await qCoro(socket).waitForDisconnected(10ms)); QCORO_VERIFY(mServer.waitForConnection()); } void testThenWaitForDisconnectedTimeout_coro(TestLoop &el) { mServer.setExpectTimeout(true); QTcpSocket socket; qCoro(socket).connectToHost(QHostAddress::LocalHost, mServer.port()).then([&](bool connected) { if (!connected) { el.quit(); } QVERIFY(connected); QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); const auto start = std::chrono::steady_clock::now(); qCoro(socket).waitForDisconnected(10ms).then([&el, start](bool disconnected) { el.quit(); QVERIFY(!disconnected); const auto end = std::chrono::steady_clock::now(); QVERIFY(end - start < 500ms); }); }); el.exec(); QVERIFY(mServer.waitForConnection()); } QCoro::Task<> testReadAllTriggers_coro(QCoro::TestContext) { QTcpSocket socket; co_await qCoro(socket).connectToHost(QHostAddress::LocalHost, mServer.port()); QCORO_COMPARE(socket.state(), QAbstractSocket::ConnectedState); socket.write("GET /stream HTTP/1.1\r\n"); QCORO_TEST_IODEVICE_READALL(socket); QCORO_VERIFY(mServer.waitForConnection()); } void testThenReadAllTriggers_coro(TestLoop &el) { QTcpSocket socket; bool called = false; qCoro(socket).connectToHost(QHostAddress::LocalHost, mServer.port()).then([&](bool connected) { if (!connected) { el.quit(); } QVERIFY(connected); QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); socket.write("GET /block HTTP/1.1\r\n"); QByteArray data; qCoro(socket).readAll().then([&](const QByteArray &data) { el.quit(); called = true; QVERIFY(!data.isEmpty()); }); }); el.exec(); QVERIFY(called); QVERIFY(mServer.waitForConnection()); } QCoro::Task<> testReadTriggers_coro(QCoro::TestContext) { QTcpSocket socket; co_await qCoro(socket).connectToHost(QHostAddress::LocalHost, mServer.port()); QCORO_COMPARE(socket.state(), QAbstractSocket::ConnectedState); socket.write("GET /stream HTTP/1.1\r\n"); QCORO_TEST_IODEVICE_READ(socket); QCORO_VERIFY(mServer.waitForConnection()); } void testThenReadTriggers_coro(TestLoop &el) { QTcpSocket socket; bool called = false; qCoro(socket).connectToHost(QHostAddress::LocalHost, mServer.port()).then([&](bool connected) { if (!connected) { el.quit(); } QVERIFY(connected); QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); socket.write("GET /block HTTP/1.1\r\n"); QByteArray data; qCoro(socket).read(1).then([&](const QByteArray &data) { el.quit(); called = true; QCOMPARE(data.size(), 1); }); }); el.exec(); QVERIFY(called); QVERIFY(mServer.waitForConnection()); } QCoro::Task<> testReadLineTriggers_coro(QCoro::TestContext) { QTcpSocket socket; co_await qCoro(socket).connectToHost(QHostAddress::LocalHost, mServer.port()); QCORO_COMPARE(socket.state(), QAbstractSocket::ConnectedState); socket.write("GET /stream HTTP/1.1\r\n"); QCORO_TEST_IODEVICE_READLINE(socket); QCORO_COMPARE(lines.size(), 14); QCORO_VERIFY(mServer.waitForConnection()); } void testThenReadLineTriggers_coro(TestLoop &el) { QTcpSocket socket; bool called = false; qCoro(socket).connectToHost(QHostAddress::LocalHost, mServer.port()).then([&](bool connected) { if (!connected) { el.quit(); } QVERIFY(connected); QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); socket.write("GET /stream HTTP/1.1\r\n"); QByteArray data; qCoro(socket).readLine().then([&](const QByteArray &data) { el.quit(); called = true; QVERIFY(!data.isEmpty()); }); }); el.exec(); QVERIFY(called); QVERIFY(mServer.waitForConnection()); } private Q_SLOTS: void init() { mServer.start(QHostAddress::LocalHost); } void cleanup() { mServer.stop(); } addCoroAndThenTests(WaitForConnectedTriggers) addCoroAndThenTests(WaitForConnectedTimeout) addCoroAndThenTests(WaitForDisconnectedTriggers) addCoroAndThenTests(WaitForDisconnectedTimeout) addCoroAndThenTests(DoesntCoAwaitConnectedSocket) addCoroAndThenTests(DoesntCoAwaitDisconnectedSocket) addCoroAndThenTests(ConnectToServerWithArgs) addCoroAndThenTests(ReadAllTriggers) addCoroAndThenTests(ReadTriggers) addCoroAndThenTests(ReadLineTriggers) private: TestHttpServer mServer; }; QTEST_GUILESS_MAIN(QCoroAbstractSocketTest) #include "qcoroabstractsocket.moc" qcoro-0.12.0/tests/qcoroasyncgenerator.cpp000066400000000000000000000223021477357142500206710ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcoroasyncgenerator.h" #include "qcorotimer.h" #include "testobject.h" #include #include #include struct Nocopymove { explicit constexpr Nocopymove(int val): val(val) {} Nocopymove(const Nocopymove &) = delete; Nocopymove &operator=(const Nocopymove &) = delete; Nocopymove(Nocopymove &&) = delete; Nocopymove &operator=(Nocopymove &&) = delete; ~Nocopymove() = default; int val; }; struct Moveonly { explicit constexpr Moveonly(int val): val(val) {} Moveonly(const Moveonly &) = delete; Moveonly &operator=(const Moveonly &) = delete; Moveonly(Moveonly &&) noexcept = default; Moveonly &operator=(Moveonly &&) noexcept = default; ~Moveonly() = default; int val; }; QCoro::Task<> sleep(std::chrono::milliseconds ms) { QTimer timer; timer.start(ms); co_await timer; } class AsyncGeneratorTest : public QCoro::TestObject { Q_OBJECT private: QCoro::Task<> testGenerator_coro(QCoro::TestContext) { const auto createGenerator = []() -> QCoro::AsyncGenerator { for (int i = 0; i < 10; i++) { QTimer timer; timer.start(50ms); co_await qCoro(timer).waitForTimeout(); co_yield i; } }; std::vector values; QCORO_FOREACH(int val, createGenerator()) { values.push_back(val); } QCORO_COMPARE(values, (std::vector{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})); } QCoro::Task<> testSyncGenerator_coro(QCoro::TestContext ctx) { ctx.setShouldNotSuspend(); const auto createGenerator = []() -> QCoro::AsyncGenerator { for (int i = 0; i < 10; ++i) { co_yield i; } }; std::vector values; QCORO_FOREACH(int val, createGenerator()) { values.push_back(val); } QCORO_COMPARE(values, (std::vector{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})); } QCoro::Task<> testTerminateSuspendedGenerator_coro(QCoro::TestContext ctx) { ctx.setShouldNotSuspend(); bool destroyed = false; const auto createGenerator = [&destroyed]() -> QCoro::AsyncGenerator { const auto guard = qScopeGuard([&destroyed]() { destroyed = true; }); const auto pointer = std::make_unique( QStringLiteral("This should get destroyed. If not, ASAN will catch it.")); while (true) { co_yield 42; } }; { auto generator = createGenerator(); const auto it = co_await generator.begin(); QCORO_COMPARE(*it, 42); } // The generator gets destroyed here. Everything on generator's stack is destroyed. QCORO_VERIFY(destroyed); } QCoro::Task<> testEmptyGenerator_coro(QCoro::TestContext ctx) { ctx.setShouldNotSuspend(); const auto createGenerator = []() -> QCoro::AsyncGenerator { if (false) { // NOLINT(readability-simplify-boolean-expr) co_yield 42; } }; auto generator = createGenerator(); QCORO_COMPARE(co_await generator.begin(), generator.end()); } QCoro::Task<> testReferenceGenerator_coro(QCoro::TestContext) { const auto createGenerator = []() -> QCoro::AsyncGenerator { for (int i = 0; i < 8; i += 2) { Nocopymove val{i}; co_await sleep(10ms); co_yield val; QCORO_COMPARE(val.val, i + 1); } }; int testvalue = 0; QCORO_FOREACH(Nocopymove &val, createGenerator()) { QCORO_COMPARE(val.val, testvalue); ++val.val; testvalue += 2; } QCORO_COMPARE(testvalue, 8); } QCoro::Task<> testConstReferenceGenerator_coro(QCoro::TestContext) { const auto createGenerator = []() -> QCoro::AsyncGenerator { for (int i = 0; i < 4; ++i) { const Nocopymove value{i}; co_await sleep(10ms); co_yield value; } }; int testvalue = 0; QCORO_FOREACH(const Nocopymove &val, createGenerator()) { QCORO_COMPARE(val.val, testvalue++); } QCORO_COMPARE(testvalue, 4); } QCoro::Task<> testMoveonlyGenerator_coro(QCoro::TestContext) { const auto createGenerator = []() -> QCoro::AsyncGenerator { for (int i = 0; i < 4; ++i) { Moveonly value{i}; co_await sleep(10ms); co_yield std::move(value); } }; auto generator = createGenerator(); int testvalue = 0; for (auto it = co_await generator.begin(), end = generator.end(); it != end; co_await ++it) { Moveonly value = std::move(*it); QCORO_COMPARE(value.val, testvalue++); } QCORO_COMPARE(testvalue, 4); } QCoro::Task<> testMovedGenerator_coro(QCoro::TestContext) { QCoro::AsyncGenerator generator; const auto createGenerator = []() -> QCoro::AsyncGenerator { for (int i = 0; i < 4; ++i) { co_await sleep(10ms); co_yield i; } }; { QCoro::AsyncGenerator originalGenerator = createGenerator(); generator = std::move(originalGenerator); } int testvalue = 0; for (auto it = co_await generator.begin(), end = generator.end(); it != end; co_await ++it) { int value = *it; QCORO_COMPARE(value, testvalue++); } QCORO_COMPARE(testvalue, 4); } QCoro::Task<> testException_coro(QCoro::TestContext) { const auto createGenerator = []() -> QCoro::AsyncGenerator { for (int i = 0; i < 4; ++i) { co_await sleep(10ms); if (i == 2) { throw std::runtime_error("Two?! I can't handle that much!"); } co_yield i; } }; auto generator = createGenerator(); auto it = co_await generator.begin(); QCORO_VERIFY(it != generator.end()); QCORO_COMPARE(*it, 0); co_await ++it; QCORO_VERIFY(it != generator.end()); QCORO_COMPARE(*it, 1); QCORO_VERIFY_EXCEPTION_THROWN(co_await ++it, std::runtime_error); QCORO_COMPARE(it, generator.end()); } QCoro::Task<> testExceptionInDereference_coro(QCoro::TestContext) { const auto createGenerator = []() -> QCoro::AsyncGenerator { for (int i = 0; i < 4; ++i) { co_await sleep(10ms); if (i == 2) { throw std::runtime_error("I already told you two is too much"); } co_yield i; } }; auto generator = createGenerator(); auto it = co_await generator.begin(); QCORO_VERIFY(it != generator.end()); QCORO_COMPARE(*it, 0); co_await ++it; QCORO_VERIFY(it != generator.end()); QCORO_COMPARE(*it, 1); co_await ++it; QCORO_VERIFY_EXCEPTION_THROWN(*it, std::runtime_error); QCORO_COMPARE(it, generator.end()); } QCoro::Task<> testExceptionInBegin_coro(QCoro::TestContext) { bool throw_exception = true; const auto createGenerator = [throw_exception]() -> QCoro::AsyncGenerator { co_await sleep(10ms); // NOTE: The condition here is a necessary workaround for Clang being too clever, // seeing that `co_yield` will never be reached and optimizing it away, thus breaking // the coroutine. With this condition (or by wrapping the body into a for-loop) the // optimization is disabled (as the co_yield could *theoretically* be reached) and // the generator behaves as expected. if (throw_exception) { throw std::runtime_error("I can't even zero!"); } co_yield 42; }; auto generator = createGenerator(); QCORO_VERIFY_EXCEPTION_THROWN(co_await generator.begin(), std::runtime_error); } QCoro::Task<> testExceptionInBeginSync_coro(QCoro::TestContext context) { context.setShouldNotSuspend(); const auto createGenerator = []() -> QCoro::AsyncGenerator { throw std::runtime_error("I can't even zero!"); co_yield 1; }; auto generator = createGenerator(); QCORO_VERIFY_EXCEPTION_THROWN(co_await generator.begin(), std::runtime_error); } private Q_SLOTS: addTest(Generator) addTest(SyncGenerator) addTest(TerminateSuspendedGenerator) addTest(EmptyGenerator) addTest(ReferenceGenerator) addTest(ConstReferenceGenerator) addTest(MoveonlyGenerator) addTest(MovedGenerator) addTest(Exception) addTest(ExceptionInDereference) addTest(ExceptionInBegin) addTest(ExceptionInBeginSync) }; QTEST_GUILESS_MAIN(AsyncGeneratorTest) #include "qcoroasyncgenerator.moc" qcoro-0.12.0/tests/qcorogenerator.cpp000066400000000000000000000137261477357142500176450ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcorogenerator.h" #include "qcorotest.h" #include #include #include struct Nocopymove { explicit constexpr Nocopymove(int val): val(val) {} Nocopymove(const Nocopymove &) = delete; Nocopymove &operator=(const Nocopymove &) = delete; Nocopymove(Nocopymove &&) = delete; Nocopymove &operator=(Nocopymove &&) = delete; ~Nocopymove() = default; int val; }; struct Moveonly { explicit constexpr Moveonly(int val): val(val) {} Moveonly(const Moveonly &) = delete; Moveonly &operator=(const Moveonly &) = delete; Moveonly(Moveonly &&) noexcept = default; Moveonly &operator=(Moveonly &&) noexcept = default; ~Moveonly() = default; int val; }; class GeneratorTest : public QObject { Q_OBJECT private Q_SLOTS: void testImmediateGenerator() { const auto createGenerator = []() -> QCoro::Generator { for (int value = 0; value < 10; ++value) { co_yield value; } }; auto generator = createGenerator(); std::vector values; for (auto it = generator.begin(), end = generator.end(); it != end; ++it) { values.emplace_back(*it); } QCOMPARE(values.size(), 10U); QCOMPARE(values, (std::vector{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})); } void testTerminateSuspendedGenerator() { bool destroyed = false; const auto createGenerator = [&destroyed]() -> QCoro::Generator { const auto guard = qScopeGuard([&destroyed]() { destroyed = true; }); const auto pointer = std::make_unique( QStringLiteral("This should get destroyed. If not, ASAN will catch it.")); while (true) { co_yield 42; } }; { auto generator = createGenerator(); const auto it = generator.begin(); QCOMPARE(*it, 42); } // The generator gets destroyed here. QVERIFY(destroyed); } void testEmptyGenerator() { const auto createGenerator = []() -> QCoro::Generator { if (false) { // NOLINT(readability-simplify-boolean-expr) co_yield 42; // Make it a coroutine, except it never gets invoked. } }; auto generator = createGenerator(); const auto begin = generator.begin(); QCOMPARE(begin, generator.end()); } void testConstReferenceGenerator() { const auto createGenerator = []() -> QCoro::Generator { for (int i = 0; i < 4; ++i) { const Nocopymove val(i); co_yield val; } }; auto generator = createGenerator(); auto it = generator.begin(); int testval = 0; while (it != generator.end()) { const Nocopymove &value = *it; QCOMPARE(value.val, testval++); ++it; } QCOMPARE(testval, 4); } void testReferenceGenerator() { const auto createGenerator = []() -> QCoro::Generator { for (int i = 0; i < 8; i += 2) { Nocopymove val(i); co_yield val; QCORO_COMPARE(val.val, i + 1); } }; auto generator = createGenerator(); auto it = generator.begin(); int testval = 0; while (it != generator.end()) { Nocopymove &value = *it; QCOMPARE(value.val, testval); value.val += 1; testval += 2; ++it; } } void testMoveonlyGenerator() { const auto createGenerator = []() -> QCoro::Generator { for (int i = 0; i < 4; ++i) { Moveonly value{i}; co_yield std::move(value); } }; auto generator = createGenerator(); auto it = generator.begin(); int testval = 0; while (it != generator.end()) { Moveonly val = std::move(*it); QCOMPARE(val.val, testval++); ++it; } QCOMPARE(testval, 4); } void testMovedGenerator() { const auto createGenerator = []() -> QCoro::Generator { for (int i = 0; i < 4; ++i) { co_yield i; } }; auto originalGenerator = createGenerator(); auto generator = std::move(originalGenerator); auto it = generator.begin(); int testval = 0; while (it != generator.end()) { int value = *it; QCOMPARE(value, testval++); ++it; } QCOMPARE(testval, 4); } void testException() { const auto createGenerator = []() -> QCoro::Generator { for (int i = 0; i < 10; ++i) { if (i == 2) { throw std::runtime_error("Two?! I can't handle two!!"); } co_yield i; } }; auto generator = createGenerator(); auto it = generator.begin(); QVERIFY(it != generator.end()); QCOMPARE(*it, 0); ++it; QVERIFY(it != generator.end()); QCOMPARE(*it, 1); #if QT_VERSION >= QT_VERSION_CHECK(6, 3, 0) QVERIFY_THROWS_EXCEPTION(std::runtime_error, ++it); #else QVERIFY_EXCEPTION_THROWN(++it, std::runtime_error); #endif QCOMPARE(it, generator.end()); } void testExceptionInBegin() { auto generator = []() -> QCoro::Generator { throw std::runtime_error("Zero is too small!"); co_yield 1; }(); #if QT_VERSION >= QT_VERSION_CHECK(6, 3, 0) QVERIFY_THROWS_EXCEPTION(std::runtime_error, generator.begin()); #else QVERIFY_EXCEPTION_THROWN(generator.begin(), std::runtime_error); #endif } }; QTEST_GUILESS_MAIN(GeneratorTest) #include "qcorogenerator.moc" qcoro-0.12.0/tests/qcoroimageprovider-qt5-lsan.supp000066400000000000000000000003661477357142500223570ustar00rootroot00000000000000# leaks in Qt 5.15.2 in the Offscreen QPA, fixed in newer Qt 5.15 leak:QPlatformScreen leak:libqoffscreen.so # leaks in Qt 5.15.2 + GCC 10, 11 leak:QObject::QObject(QObject*) leak:QtSharedPointer::ExternalRefCountData::getAndRef(QObject const*) qcoro-0.12.0/tests/qcoroimageprovider.cpp000066400000000000000000000053621477357142500205110ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "testobject.h" #include "qcoro/core/qcorotimer.h" #include "qcoro/quick/qcoroimageprovider.h" #include #include #include #define emit Q_EMIT // HACK: the private header uses `emit`, but QCoro is built with QT_NO_KEYWORDS #include #undef emit class TestImageProvider final: public QCoro::ImageProvider { public: TestImageProvider(bool async, const QString &error) : mAsync(async), mError(error) {} protected: QCoro::Task asyncRequestImage(const QString &id, const QSize &requestedSize) override { Q_UNUSED(id); Q_UNUSED(requestedSize); if (mAsync) { QTimer timer; timer.start(250ms); co_await timer; } co_return QImage{}; } private: bool mAsync; QString mError; }; class QCoroImageProviderTest: public QCoro::TestObject { Q_OBJECT private Q_SLOTS: void testImageProvider_data() { QTest::addColumn("id"); QTest::addColumn("async"); QTest::addColumn("error"); QTest::newRow("sync") << QStringLiteral("image-sync.jpg") << false << QString(); QTest::newRow("async") << QStringLiteral("image-async.jpg") << true << QString(); } void testImageProvider() { QFETCH(QString, id); QFETCH(bool, async); QFETCH(QString, error); QString source = QStringLiteral("image://qcorotest/%1.jpg").arg(id); auto provider = new TestImageProvider(async, error); QQmlEngine engine; engine.addImageProvider(QStringLiteral("qcorotest"), provider); QVERIFY(engine.imageProvider(QStringLiteral("qcorotest")) != nullptr); const QString componentStr = QStringLiteral(R"( import QtQuick 2.0 Image { source: "%1"; asynchronous: %2; })").arg(source, async ? QStringLiteral("true") : QStringLiteral("false")); QQmlComponent component(&engine); component.setData(componentStr.toLatin1(), QUrl::fromLocalFile(QStringLiteral(""))); std::unique_ptr image{qobject_cast(component.create())}; QVERIFY(image != nullptr); if (async) { QTRY_COMPARE(image->status(), QQuickImage::Loading); } QCOMPARE(image->source(), QUrl(source)); if (error.isEmpty()) { QTRY_COMPARE(image->status(), QQuickImage::Ready); QCOMPARE(image->progress(), 1.0); } else { QTRY_COMPARE(image->status(), QQuickImage::Error); } } private: }; QTEST_MAIN(QCoroImageProviderTest) #include "qcoroimageprovider.moc" qcoro-0.12.0/tests/qcoroiodevice_macros.h000066400000000000000000000062011477357142500204450ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "testobject.h" #define QCORO_TEST_IODEVICE_READALL(device) \ QByteArray data; \ Q_FOREVER { \ const auto buf = co_await qCoro((device)).readAll(); \ if (buf.isNull()) { \ break; \ } \ data += buf; \ } \ QCORO_VERIFY(!data.isEmpty()); \ QCORO_COMPARE((device).bytesAvailable(), 0) #define QCORO_TEST_IODEVICE_READ(device) \ QByteArray data; \ Q_FOREVER { \ const auto buf = co_await qCoro((device)).read(1); \ if (buf.isNull()) { \ break; \ } \ data += buf; \ } \ QCORO_VERIFY(!data.isEmpty()); \ QCORO_COMPARE((device).bytesAvailable(), 0) #define QCORO_TEST_IODEVICE_READLINE(device) \ QByteArrayList lines; \ Q_FOREVER { \ const auto buf = co_await qCoro((device)).readLine(); \ if (buf.isNull()) { \ break; \ } \ lines.push_back(buf); \ } \ QCORO_COMPARE((device).bytesAvailable(), 0) qcoro-0.12.0/tests/qcorolazytask.cpp000066400000000000000000000114241477357142500175120ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcorotest.h" #include "testobject.h" #include "qcorotimer.h" #include #include "qcorolazytask.h" using namespace std::chrono_literals; class QCoroLazyTaskTest : public QCoro::TestObject { Q_OBJECT private: QCoro::Task<> testSyncLazyCoroutineStarts_coro(QCoro::TestContext context) { context.setShouldNotSuspend(); constexpr auto coro = [](bool &started) -> QCoro::LazyTask<> { started = true; co_return; }; bool started = false; const auto task = coro(started); QCORO_VERIFY(!started); co_await task; QCORO_VERIFY(started); } QCoro::Task<> testLazyCoroutineStarts_coro(QCoro::TestContext) { constexpr auto coro = [](bool &started, bool &resumed) -> QCoro::LazyTask<> { started = true; co_await QCoro::sleepFor(1ms); resumed = true; }; bool started = false; bool resumed = false; const auto task = coro(started, resumed); QCORO_VERIFY(!started); co_await task; QCORO_VERIFY(started); QCORO_VERIFY(resumed); } QCoro::Task<> testNonVoidSyncLazyCoroutineStarts_coro(QCoro::TestContext context) { context.setShouldNotSuspend(); constexpr auto coro = [](bool &started) -> QCoro::LazyTask { started = true; co_return 42; }; bool started = false; const auto task = coro(started); QCORO_VERIFY(!started); const auto result = co_await task; QCORO_VERIFY(started); QCORO_COMPARE(result, 42); } QCoro::Task<> testNonVoidLazyCoroutineStarts_coro(QCoro::TestContext) { constexpr auto coro = [](bool &started, bool &resumed) -> QCoro::LazyTask { started = true; co_await QCoro::sleepFor(1ms); resumed = true; co_return 42; }; bool started = false; bool resumed = false; const auto task = coro(started, resumed); QCORO_VERIFY(!started); const auto result = co_await task; QCORO_VERIFY(started); QCORO_VERIFY(resumed); QCORO_COMPARE(result, 42); } QCoro::Task<> testEagerInsideLazy_coro(QCoro::TestContext) { constexpr auto coro = []() -> QCoro::LazyTask { co_return co_await []() -> QCoro::Task { co_await QCoro::sleepFor(1ms); co_return 42; }(); }; const auto task = coro(); const auto result = co_await task; QCORO_COMPARE(result, 42); } QCoro::Task<> testThenLazyContinuation_coro(QCoro::TestContext) { constexpr auto coro = []() -> QCoro::LazyTask { co_await QCoro::sleepFor(1ms); co_return 42; }; const auto task = coro().then([](int result) -> QCoro::LazyTask { co_await QCoro::sleepFor(1ms); co_return QString::number(result); }); const auto result = co_await task; QCORO_COMPARE(result, QStringLiteral("42")); } QCoro::Task<> testThenEagerContinuation_coro(QCoro::TestContext) { constexpr auto coro = []() -> QCoro::LazyTask { co_await QCoro::sleepFor(1ms); co_return 42; }; const auto task = coro().then([](int result) -> QCoro::Task { co_await QCoro::sleepFor(1ms); co_return result; }); const auto result = co_await task; QCORO_COMPARE(result, 42); } QCoro::Task<> testThenNonCoroutineContinuation_coro(QCoro::TestContext) { constexpr auto coro = []() -> QCoro::LazyTask { co_await QCoro::sleepFor(1ms); co_return 42; }; const auto task = coro().then([](int result) { return QString::number(result); }); static_assert(std::is_same_v>); const auto result = co_await task; QCORO_COMPARE(result, QStringLiteral("42")); } private Q_SLOTS: addTest(SyncLazyCoroutineStarts) addTest(LazyCoroutineStarts) addTest(NonVoidSyncLazyCoroutineStarts) addTest(NonVoidLazyCoroutineStarts) addTest(EagerInsideLazy) addTest(ThenLazyContinuation) addTest(ThenEagerContinuation) addTest(ThenNonCoroutineContinuation) void testWaitFor() { auto coro = []() -> QCoro::LazyTask { co_await QCoro::sleepFor(1ms); co_return 42; }; const auto result = QCoro::waitFor(coro()); QCOMPARE(result, 42); } }; QTEST_GUILESS_MAIN(QCoroLazyTaskTest) #include "qcorolazytask.moc" qcoro-0.12.0/tests/qcorolocalsocket.cpp000066400000000000000000000274461477357142500201660ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "testhttpserver.h" #include "testobject.h" #include "qcoroiodevice_macros.h" #include "testloop.h" #include "qcoro/network/qcorolocalsocket.h" #include #include #include static const QByteArray blockRequest = "GET /block HTTP/1.1\r\n"; static const QByteArray streamRequest = "GET /stream HTTP/1.1\r\n"; class QCoroLocalSocketTest : public QCoro::TestObject { Q_OBJECT private: QCoro::Task<> testWaitForConnectedTriggers_coro(QCoro::TestContext) { QLocalSocket socket; QCORO_DELAY(socket.connectToServer(QCoroLocalSocketTest::getSocketName())); co_await qCoro(socket).waitForConnected(); QCORO_COMPARE(socket.state(), QLocalSocket::ConnectedState); QCORO_VERIFY(mServer.waitForConnection()); } void testThenWaitForConnectedTriggers_coro(TestLoop &el) { QLocalSocket socket; QCORO_DELAY(socket.connectToServer(QCoroLocalSocketTest::getSocketName())); bool called = false; qCoro(socket).waitForConnected().then([&](bool connected) { called = true; el.quit(); QVERIFY(connected); }); el.exec(); QVERIFY(called); QVERIFY(mServer.waitForConnection()); } QCoro::Task<> testWaitForDisconnectedTriggers_coro(QCoro::TestContext) { QLocalSocket socket; socket.connectToServer(QCoroLocalSocketTest::getSocketName()); QCORO_COMPARE(socket.state(), QLocalSocket::ConnectedState); QCORO_DELAY(socket.disconnectFromServer()); co_await qCoro(socket).waitForDisconnected(); QCORO_COMPARE(socket.state(), QLocalSocket::UnconnectedState); QCORO_VERIFY(mServer.waitForConnection()); } void testThenWaitForDisconnectedTriggers_coro(TestLoop &el) { QLocalSocket socket; socket.connectToServer(QCoroLocalSocketTest::getSocketName()); QCOMPARE(socket.state(), QLocalSocket::ConnectedState); QCORO_DELAY(socket.disconnectFromServer()); bool called = false; qCoro(socket).waitForDisconnected().then([&](bool disconnected) { called = true; el.quit(); QVERIFY(disconnected); }); el.exec(); QVERIFY(called); QVERIFY(mServer.waitForConnection()); } // On Linux at least, QLocalSocket connects immediately and synchronously QCoro::Task<> testDoesntCoAwaitConnectedSocket_coro(QCoro::TestContext context) { context.setShouldNotSuspend(); QLocalSocket socket; socket.connectToServer(QCoroLocalSocketTest::getSocketName()); QCORO_COMPARE(socket.state(), QLocalSocket::ConnectedState); co_await qCoro(socket).waitForConnected(); QCORO_VERIFY(mServer.waitForConnection()); } void testThenDoesntCoAwaitConnectedSocket_coro(TestLoop &el) { QLocalSocket socket; socket.connectToServer(QCoroLocalSocketTest::getSocketName()); QCOMPARE(socket.state(), QLocalSocket::ConnectedState); bool called = false; qCoro(socket).waitForConnected().then([&](bool connected) { called = true; el.quit(); QVERIFY(connected); }); el.exec(); QVERIFY(called); QVERIFY(mServer.waitForConnection()); } QCoro::Task<> testDoesntCoAwaitDisconnectedSocket_coro(QCoro::TestContext context) { context.setShouldNotSuspend(); mServer.setExpectTimeout(true); QLocalSocket socket; QCORO_COMPARE(socket.state(), QLocalSocket::UnconnectedState); co_await qCoro(socket).waitForDisconnected(); } void testThenDoesntCoAwaitDisconnectedSocket_coro(TestLoop &el) { mServer.setExpectTimeout(true); QLocalSocket socket; QCOMPARE(socket.state(), QLocalSocket::UnconnectedState); bool called = false; qCoro(socket).waitForDisconnected().then([&](bool disconnected) { called = true; el.quit(); QVERIFY(!disconnected); }); el.exec(); QVERIFY(called); } QCoro::Task<> testConnectToServerWithArgs_coro(QCoro::TestContext context) { context.setShouldNotSuspend(); QLocalSocket socket; co_await qCoro(socket).connectToServer(QCoroLocalSocketTest::getSocketName()); QCORO_COMPARE(socket.state(), QLocalSocket::ConnectedState); QCORO_VERIFY(mServer.waitForConnection()); } void testThenConnectToServerWithArgs_coro(TestLoop &el) { QLocalSocket socket; bool called = false; qCoro(socket).connectToServer(QCoroLocalSocketTest::getSocketName()).then([&](bool connected) { called = true; el.quit(); QVERIFY(connected); }); el.exec(); QVERIFY(called); QVERIFY(mServer.waitForConnection()); } QCoro::Task<> testConnectToServer_coro(QCoro::TestContext context) { context.setShouldNotSuspend(); QLocalSocket socket; socket.setServerName(QCoroLocalSocketTest::getSocketName()); co_await qCoro(socket).connectToServer(); QCORO_COMPARE(socket.state(), QLocalSocket::ConnectedState); QCORO_VERIFY(mServer.waitForConnection()); } void testThenConnectToServer_coro(TestLoop &el) { QLocalSocket socket; socket.setServerName(QCoroLocalSocketTest::getSocketName()); bool called = false; qCoro(socket).connectToServer().then([&](bool connected) { called = true; el.quit(); QVERIFY(connected); }); el.exec(); QVERIFY(called); QVERIFY(mServer.waitForConnection()); } QCoro::Task<> testWaitForConnectedTimeout_coro(QCoro::TestContext) { mServer.setExpectTimeout(true); QLocalSocket socket; QCORO_TEST_TIMEOUT(co_await qCoro(socket).waitForConnected(10ms)); } void testThenWaitForConnectedTimeout_coro(TestLoop &el) { mServer.setExpectTimeout(true); QLocalSocket socket; bool called = false; qCoro(socket).waitForConnected(10ms).then([&](bool connected) { called = true; el.quit(); QVERIFY(!connected); }); const auto start = std::chrono::steady_clock::now(); el.exec(); const auto end = std::chrono::steady_clock::now(); QVERIFY(end - start < 500ms); QVERIFY(called); } QCoro::Task<> testWaitForDisconnectedTimeout_coro(QCoro::TestContext) { QLocalSocket socket; socket.connectToServer(QCoroLocalSocketTest::getSocketName()); QCORO_COMPARE(socket.state(), QLocalSocket::ConnectedState); QCORO_TEST_TIMEOUT(co_await qCoro(socket).waitForDisconnected(10ms)); QCORO_VERIFY(mServer.waitForConnection()); } void testThenWaitForDisconnectedTimeout_coro(TestLoop &el) { QLocalSocket socket; socket.connectToServer(QCoroLocalSocketTest::getSocketName()); QCOMPARE(socket.state(), QLocalSocket::ConnectedState); bool called = false; qCoro(socket).waitForDisconnected(10ms).then([&](bool disconnected) { called = true; el.quit(); QVERIFY(!disconnected); }); const auto start = std::chrono::steady_clock::now(); el.exec(); const auto end = std::chrono::steady_clock::now(); QVERIFY(end - start < 500ms); QVERIFY(called); QVERIFY(mServer.waitForConnection()); } QCoro::Task<> testReadAllTriggers_coro(QCoro::TestContext) { QLocalSocket socket; socket.connectToServer(QCoroLocalSocketTest::getSocketName()); QCORO_COMPARE(socket.state(), QLocalSocket::ConnectedState); const auto written = co_await qCoro(socket).write(streamRequest); QCORO_COMPARE(written, streamRequest.size()); QCORO_TEST_IODEVICE_READALL(socket); QCORO_VERIFY(mServer.waitForConnection()); } void testThenReadAllTriggers_coro(TestLoop &el) { QLocalSocket socket; socket.connectToServer(QCoroLocalSocketTest::getSocketName()); QCOMPARE(socket.state(), QLocalSocket::ConnectedState); bool called = false; qCoro(socket).readAll().then([&](const QByteArray &data) { called = true; el.quit(); QVERIFY(!data.isEmpty()); }); qCoro(socket).write(blockRequest).then([&](qint64 written) { QCOMPARE(written, blockRequest.size()); }); el.exec(); QVERIFY(called); QVERIFY(mServer.waitForConnection()); } QCoro::Task<> testReadTriggers_coro(QCoro::TestContext) { QLocalSocket socket; socket.connectToServer(QCoroLocalSocketTest::getSocketName()); QCORO_COMPARE(socket.state(), QLocalSocket::ConnectedState); const auto written = co_await qCoro(socket).write(streamRequest); QCORO_COMPARE(written, streamRequest.size()); QCORO_TEST_IODEVICE_READ(socket); QCORO_VERIFY(mServer.waitForConnection()); } void testThenReadTriggers_coro(TestLoop &el) { QLocalSocket socket; socket.connectToServer(QCoroLocalSocketTest::getSocketName()); QCOMPARE(socket.state(), QLocalSocket::ConnectedState); bool called = false; qCoro(socket).read(1).then([&](const QByteArray &data) { called = true; el.quit(); QCOMPARE(data.size(), 1); }); qCoro(socket).write(blockRequest).then([&](qint64 written) { QCOMPARE(written, blockRequest.size()); }); el.exec(); QVERIFY(called); QVERIFY(mServer.waitForConnection()); } QCoro::Task<> testReadLineTriggers_coro(QCoro::TestContext) { QLocalSocket socket; socket.connectToServer(QCoroLocalSocketTest::getSocketName()); QCORO_COMPARE(socket.state(), QLocalSocket::ConnectedState); const auto written = co_await qCoro(socket).write(streamRequest); QCORO_COMPARE(written, streamRequest.size()); QCORO_TEST_IODEVICE_READLINE(socket); QCORO_COMPARE(lines.size(), 14); QCORO_VERIFY(mServer.waitForConnection()); } void testThenReadLineTriggers_coro(TestLoop &el) { QLocalSocket socket; socket.connectToServer(QCoroLocalSocketTest::getSocketName()); QCOMPARE(socket.state(), QLocalSocket::ConnectedState); bool called = false; qCoro(socket).readLine().then([&](const QByteArray &data) { called = true; el.quit(); QVERIFY(!data.isEmpty()); }); qCoro(socket).write(blockRequest).then([&](qint64 written) { QCOMPARE(written, blockRequest.size()); }); el.exec(); QVERIFY(called); QVERIFY(mServer.waitForConnection()); } private Q_SLOTS: void init() { mServer.start(QCoroLocalSocketTest::getSocketName()); } void cleanup() { mServer.stop(); } addCoroAndThenTests(WaitForConnectedTriggers) addCoroAndThenTests(WaitForConnectedTimeout) addCoroAndThenTests(WaitForDisconnectedTriggers) addCoroAndThenTests(WaitForDisconnectedTimeout) addCoroAndThenTests(DoesntCoAwaitConnectedSocket) addCoroAndThenTests(DoesntCoAwaitDisconnectedSocket) addCoroAndThenTests(ConnectToServerWithArgs) addCoroAndThenTests(ConnectToServer) addCoroAndThenTests(ReadAllTriggers) addCoroAndThenTests(ReadTriggers) addCoroAndThenTests(ReadLineTriggers) private: static QString getSocketName() { return QStringLiteral("%1-%2") .arg(QCoreApplication::applicationName()) .arg(QCoreApplication::applicationPid()); } TestHttpServer mServer; }; QTEST_GUILESS_MAIN(QCoroLocalSocketTest) #include "qcorolocalsocket.moc" qcoro-0.12.0/tests/qcoronetworkreply.cpp000066400000000000000000000147531477357142500204250ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "testhttpserver.h" #include "testobject.h" #include "qcoroiodevice_macros.h" #include "qcoro/network/qcoronetworkreply.h" #include #include #include class QCoroNetworkReplyTest : public QCoro::TestObject { Q_OBJECT private: QCoro::Task<> testTriggers_coro(QCoro::TestContext) { QNetworkAccessManager nam; auto reply = std::unique_ptr(nam.get(buildRequest())); co_await reply.get(); QCORO_VERIFY(reply->isFinished()); QCORO_COMPARE(reply->error(), QNetworkReply::NoError); QCORO_COMPARE(reply->readAll(), "abcdef"); } QCoro::Task<> testQCoroWrapperTriggers_coro(QCoro::TestContext) { QNetworkAccessManager nam; auto reply = std::unique_ptr(nam.get(buildRequest())); co_await qCoro(reply.get()).waitForFinished(); QCORO_VERIFY(reply->isFinished()); QCORO_COMPARE(reply->error(), QNetworkReply::NoError); QCORO_COMPARE(reply->readAll(), "abcdef"); } void testThenQCoroWrapperTriggers_coro(TestLoop &el) { QNetworkAccessManager nam; auto reply = std::unique_ptr(nam.get(buildRequest())); bool called = false; qCoro(reply.get()).waitForFinished().then([&](bool finished) { called = true; el.quit(); QVERIFY(finished); }); el.exec(); QVERIFY(reply->isFinished()); QCOMPARE(reply->error(), QNetworkReply::NoError); QCOMPARE(reply->readAll(), "abcdef"); QVERIFY(called); } QCoro::Task<> testDoesntBlockEventLoop_coro(QCoro::TestContext) { QCoro::EventLoopChecker eventLoopResponsive; QNetworkAccessManager nam; auto reply = std::unique_ptr( nam.get(buildRequest(QStringLiteral("block")))); co_await reply.get(); QCORO_VERIFY(eventLoopResponsive); QCORO_VERIFY(reply->isFinished()); QCORO_COMPARE(reply->error(), QNetworkReply::NoError); QCORO_COMPARE(reply->readAll(), "abcdef"); } QCoro::Task<> testDoesntCoAwaitNullReply_coro(QCoro::TestContext test) { test.setShouldNotSuspend(); mServer.setExpectTimeout(true); QNetworkReply *reply = nullptr; co_await reply; delete reply; } QCoro::Task<> testDoesntCoAwaitFinishedReply_coro(QCoro::TestContext test) { QNetworkAccessManager nam; auto reply = std::unique_ptr(nam.get(buildRequest())); co_await reply.get(); QCORO_VERIFY(reply->isFinished()); test.setShouldNotSuspend(); co_await reply.get(); } QCoro::Task<> testReadAllTriggers_coro(QCoro::TestContext) { QNetworkAccessManager nam; auto reply = std::unique_ptr( nam.get(buildRequest(QStringLiteral("stream")))); QCORO_TEST_IODEVICE_READALL(*reply); QCORO_COMPARE(data.size(), reply->rawHeader("Content-Length").toInt()); } void testThenReadAllTriggers_coro(TestLoop &el) { QNetworkAccessManager nam; auto reply = std::unique_ptr( nam.get(buildRequest(QStringLiteral("block")))); bool called = false; qCoro(reply.get()).readAll().then([&](const QByteArray &data) { called = true; el.quit(); QVERIFY(!data.isEmpty()); }); el.exec(); QVERIFY(called); } QCoro::Task<> testReadTriggers_coro(QCoro::TestContext) { QNetworkAccessManager nam; auto reply = std::unique_ptr( nam.get(buildRequest(QStringLiteral("stream")))); QCORO_TEST_IODEVICE_READ(*reply); QCORO_COMPARE(data.size(), reply->rawHeader("Content-Length").toInt()); } void testThenReadTriggers_coro(TestLoop &el) { QNetworkAccessManager nam; auto reply = std::unique_ptr( nam.get(buildRequest(QStringLiteral("block")))); bool called = false; qCoro(reply.get()).read(1).then([&](const QByteArray &data) { called = true; el.quit(); QCOMPARE(data.size(), 1); }); el.exec(); QVERIFY(called); } QCoro::Task<> testReadLineTriggers_coro(QCoro::TestContext) { QNetworkAccessManager nam; auto reply = std::unique_ptr( nam.get(buildRequest(QStringLiteral("stream")))); QCORO_TEST_IODEVICE_READLINE(*reply); QCORO_COMPARE(lines.size(), 10); } void testThenReadLineTriggers_coro(TestLoop &el) { QNetworkAccessManager nam; auto reply = std::unique_ptr( nam.get(buildRequest(QStringLiteral("block")))); bool called = false; qCoro(reply.get()).readLine().then([&](const QByteArray &data) { called = true; el.quit(); QVERIFY(!data.isEmpty()); }); el.exec(); QVERIFY(called); } // See https://github.com/danvratil/qcoro/issues/231 QCoro::Task<> testAbortOnTimeout_coro(QCoro::TestContext) { auto request = buildRequest(QStringLiteral("block")); request.setTransferTimeout(300); QNetworkAccessManager nam; auto reply = co_await nam.get(request); QCORO_VERIFY(reply != nullptr); QCORO_VERIFY(reply->isFinished()); QCORO_COMPARE(reply->error(), QNetworkReply::OperationCanceledError); // QNAM is destroyed here and so is all its associated state, which could // crash (or cause invalid memory access) } private Q_SLOTS: void init() { mServer.start(QHostAddress::LocalHost); } void cleanup() { mServer.stop(); } addTest(Triggers) addCoroAndThenTests(QCoroWrapperTriggers) addTest(DoesntBlockEventLoop) addTest(DoesntCoAwaitNullReply) addTest(DoesntCoAwaitFinishedReply) addCoroAndThenTests(ReadAllTriggers) addCoroAndThenTests(ReadTriggers) addCoroAndThenTests(ReadLineTriggers) addTest(AbortOnTimeout) private: QNetworkRequest buildRequest(const QString &path = QString()) { return QNetworkRequest{ QUrl{QStringLiteral("http://127.0.0.1:%1/%2").arg(mServer.port()).arg(path)} }; } TestHttpServer mServer; }; QTEST_GUILESS_MAIN(QCoroNetworkReplyTest) #include "qcoronetworkreply.moc" qcoro-0.12.0/tests/qcoroprocess.cpp000066400000000000000000000160371477357142500173330ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "testobject.h" #include "qcoro/core/qcoroprocess.h" #include #ifdef Q_OS_WIN // There's no equivalent to "true" command on Windows, so do a single ping to localhost instead, // which terminates almost immediately. #define DUMMY_EXEC QStringLiteral("ping") #define DUMMY_ARGS \ { QStringLiteral("127.0.0.1"), QStringLiteral("-n"), QStringLiteral("1") } // On windows, the equivalent to Linux "sleep" is "timeout", but it fails due to QProcess redirecting // stdin, which "timeout" doesn't support (it waits for keypress to interrupt). However, "ping" pings // every second, so specifying number of pings to the desired timeout makes it behave basically like // the Linux "sleep". #define SLEEP_EXEC QStringLiteral("ping") #define SLEEP_ARGS(timeout) \ { QStringLiteral("127.0.0.1"), QStringLiteral("-n"), QString::number(timeout) } #else #define DUMMY_EXEC QStringLiteral("true") #define DUMMY_ARGS {} #define SLEEP_EXEC QStringLiteral("sleep") #define SLEEP_ARGS(timeout) { QString::number(timeout) } #endif class QCoroProcessTest : public QCoro::TestObject { Q_OBJECT private: QCoro::Task<> testStartTriggers_coro(QCoro::TestContext context) { #ifdef Q_OS_WIN // QProcess::start() on Windows is synchronous, despite what the documentation says, // so the coroutine will not suspend. context.setShouldNotSuspend(); #else Q_UNUSED(context); #endif QProcess process; const bool ok = co_await qCoro(process).start(DUMMY_EXEC, DUMMY_ARGS); QCORO_VERIFY(ok); QCORO_COMPARE(process.state(), QProcess::Running); process.waitForFinished(); } void testThenStartTriggers_coro(TestLoop &el) { QProcess process; bool called = false; qCoro(process).start(DUMMY_EXEC, DUMMY_ARGS).then([&](bool started) { called = true; el.quit(); QVERIFY(started); QCOMPARE(process.state(), QProcess::Running); }); el.exec(); QVERIFY(called); process.waitForFinished(); } QCoro::Task<> testStartNoArgsTriggers_coro(QCoro::TestContext context) { #ifdef Q_OS_WIN context.setShouldNotSuspend(); #else Q_UNUSED(context); #endif QProcess process; process.setProgram(DUMMY_EXEC); process.setArguments(DUMMY_ARGS); const bool ok = co_await qCoro(process).start(); QCORO_VERIFY(ok); QCORO_COMPARE(process.state(), QProcess::Running); process.waitForFinished(); } void testThenStartNoArgsTriggers_coro(TestLoop &el) { QProcess process; process.setProgram(DUMMY_EXEC); process.setArguments(DUMMY_ARGS); bool called = false; qCoro(process).start().then([&](bool started) { called = true; el.quit(); QVERIFY(started); QCOMPARE(process.state(), QProcess::Running); }); el.exec(); QVERIFY(called); process.waitForFinished(); } QCoro::Task<> testStartDoesntBlock_coro(QCoro::TestContext) { QCoro::EventLoopChecker eventLoopResponsive{1, 0ms}; QProcess process; const bool ok = co_await qCoro(process).start(DUMMY_EXEC, DUMMY_ARGS); QCORO_VERIFY(ok); QCORO_VERIFY(eventLoopResponsive); process.waitForFinished(); } QCoro::Task<> testStartDoesntCoAwaitRunningProcess_coro(QCoro::TestContext ctx) { QProcess process; #if defined(__GNUC__) && !defined(__clang__) #pragma message "Workaround for GCC ICE!" // Workaround GCC bug https://bugzilla.redhat.com/1952671 // GCC ICEs at the end of this function due to presence of two co_await statements. process.start(SLEEP_EXEC, SLEEP_ARGS(1)); process.waitForStarted(); #else const bool ok = co_await qCoro(process).start(SLEEP_EXEC, SLEEP_ARGS(1)); QCORO_VERIFY(ok); #endif QCORO_COMPARE(process.state(), QProcess::Running); ctx.setShouldNotSuspend(); QTest::ignoreMessage(QtWarningMsg, "QProcess::start: Process is already running"); co_await qCoro(process).start(); process.waitForFinished(); } QCoro::Task<> testFinishTriggers_coro(QCoro::TestContext) { QProcess process; process.start(SLEEP_EXEC, SLEEP_ARGS(1)); process.waitForStarted(); QCORO_COMPARE(process.state(), QProcess::Running); const auto ok = co_await qCoro(process).waitForFinished(); QCORO_VERIFY(ok); QCORO_COMPARE(process.state(), QProcess::NotRunning); } void testThenFinishTriggers_coro(TestLoop &el) { QProcess process; process.start(SLEEP_EXEC, SLEEP_ARGS(1)); process.waitForStarted(); QCOMPARE(process.state(), QProcess::Running); bool called = false; qCoro(process).waitForFinished().then([&](bool finished) { called = true; el.quit(); QVERIFY(finished); QCOMPARE(process.state(), QProcess::NotRunning); QCOMPARE(process.exitStatus(), QProcess::NormalExit); }); el.exec(); QVERIFY(called); } QCoro::Task<> testFinishDoesntCoAwaitFinishedProcess_coro(QCoro::TestContext ctx) { QProcess process; process.start(DUMMY_EXEC, QStringList DUMMY_ARGS); process.waitForFinished(); ctx.setShouldNotSuspend(); const auto ok = co_await qCoro(process).waitForFinished(); QCORO_VERIFY(!ok); } QCoro::Task<> testFinishCoAwaitTimeout_coro(QCoro::TestContext) { QProcess process; process.start(SLEEP_EXEC, SLEEP_ARGS(2)); process.waitForStarted(); QCORO_COMPARE(process.state(), QProcess::Running); const auto ok = co_await qCoro(process).waitForFinished(1s); QCORO_VERIFY(!ok); QCORO_COMPARE(process.state(), QProcess::Running); process.waitForFinished(); } void testThenFinishCoAwaitTimeout_coro(TestLoop &el) { QProcess process; process.start(SLEEP_EXEC, SLEEP_ARGS(2)); process.waitForStarted(); QCOMPARE(process.state(), QProcess::Running); bool called = false; qCoro(process).waitForFinished(1s).then([&](bool finished) { called = true; el.quit(); QVERIFY(!finished); }); el.exec(); QVERIFY(called); process.waitForFinished(); } private Q_SLOTS: addCoroAndThenTests(StartTriggers) addCoroAndThenTests(StartNoArgsTriggers) #ifndef Q_OS_WIN // start always blocks on Windows addTest(StartDoesntBlock) #endif addTest(StartDoesntCoAwaitRunningProcess) addCoroAndThenTests(FinishTriggers) addTest(FinishDoesntCoAwaitFinishedProcess) addCoroAndThenTests(FinishCoAwaitTimeout) }; QTEST_GUILESS_MAIN(QCoroProcessTest) #include "qcoroprocess.moc" qcoro-0.12.0/tests/qcoroqmltask.cpp000066400000000000000000000071711477357142500173300ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Jonah Brüchert // // SPDX-License-Identifier: MIT #include "qcoroqml.h" #include "qcorotask.h" #include "qcorotimer.h" #include "qcoroqmltask.h" #include "qcorofuture.h" #include #include #include #include #include using namespace std::chrono_literals; class QmlObject : public QObject { Q_OBJECT public: Q_INVOKABLE QCoro::QmlTask startTimer() { auto *timer = new QTimer(this); timer->setSingleShot(true); timer->start(1s); return [timer]() -> QCoro::Task<> { co_await timer; }(); } Q_INVOKABLE QCoro::QmlTask qmlTaskFromTimer() { auto *timer = new QTimer(this); timer->setSingleShot(true); timer->start(1s); return timer; } Q_INVOKABLE QCoro::QmlTask qmlTaskFromFuture() { QFutureInterface interface; interface.reportResult(QStringLiteral("Success")); interface.reportFinished(); return interface.future(); } Q_INVOKABLE void reportTestSuccess() { numTestsPassed++; if (numTestsPassed == 4) { // Number of java script functions that call reportTestSuccess Q_EMIT success(); } } Q_SIGNAL void success(); private: int numTestsPassed = 0; }; class QCoroQmlTaskTest : public QObject { Q_OBJECT private: Q_SLOT void testQmlCallback() { QQmlApplicationEngine engine; qmlRegisterSingletonType("qcoro.test", 0, 1, "QmlObject", [](QQmlEngine *, QJSEngine *) { return new QmlObject(); }); QCoro::Qml::registerTypes(); engine.loadData(R"( import qcoro.test 0.1 import QCoro 0 import QtQuick 2.7 QtObject { property string value: QmlObject.qmlTaskFromFuture().await("Loading...").value property string valueWithoutIntermediate: QmlObject.qmlTaskFromFuture().await().value onValueChanged: { if (value == "Success") { console.log("awaiting finished") QmlObject.reportTestSuccess() } } Component.onCompleted: { QmlObject.startTimer().then(() => { console.log("QCoro::Task JavaScript callback called") QmlObject.reportTestSuccess() }) QmlObject.qmlTaskFromTimer().then(() => { console.log("QTimer JavaScript callback called") QmlObject.reportTestSuccess() }) QmlObject.qmlTaskFromFuture().then(() => { console.log("QFuture JavaScript callback called") QmlObject.reportTestSuccess() }) } } )"); auto *object = engine.singletonInstance(qmlTypeId("qcoro.test", 0, 1, "QmlObject")); auto *timeout = new QTimer(this); timeout->setSingleShot(true); timeout->setInterval(2s); timeout->start(); bool running = true; // End the event loop normally connect(object, &QmlObject::success, this, [&]() { timeout->stop(); running = false; }); // Crash the test in case the timeout was reachaed without the callback being called connect(timeout, &QTimer::timeout, this, [&]() { #if defined(Q_CC_CLANG) && defined(Q_OS_WINDOWS) running = false; QEXPECT_FAIL("", "QTBUG-91768", Abort); QVERIFY(false); return; #else QFAIL("Timeout waiting for QML continuation to be called"); #endif }); while (running) { QCoreApplication::processEvents(); } } }; QTEST_GUILESS_MAIN(QCoroQmlTaskTest) #include "qcoroqmltask.moc" qcoro-0.12.0/tests/qcorosignal.cpp000066400000000000000000000341101477357142500171220ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "testobject.h" #include "qcoro/core/qcorotimer.h" #include "qcoro/core/qcorosignal.h" #include #include using namespace std::chrono_literals; class SignalTest : public QObject { Q_OBJECT public: explicit SignalTest(bool active = true) { if (active) { QTimer::singleShot(100ms, this, &SignalTest::emit); } } void emit() { Q_EMIT voidSignal(); Q_EMIT singleArg(QStringLiteral("YAY!")); Q_EMIT multiArg(QStringLiteral("YAY!"), 42, this); Q_EMIT privateVoid(QPrivateSignal{}); Q_EMIT privateSingleArg(QStringLiteral("YAY!"), QPrivateSignal{}); Q_EMIT privateMultiArg(QStringLiteral("YAY!"), 42, this, QPrivateSignal{}); } Q_SIGNALS: void voidSignal(); void singleArg(const QString &); void multiArg(const QString &, int, QObject *); void privateVoid(QPrivateSignal); void privateSingleArg(const QString &, QPrivateSignal); void privateMultiArg(const QString &, int, QObject *, QPrivateSignal); void signalThatsNeverEmitted(); }; class MultiSignalTest : public SignalTest { Q_OBJECT public: explicit MultiSignalTest(bool active = true) : SignalTest(false) { if (active) { mTimer.setInterval(10ms); connect(&mTimer, &QTimer::timeout, this, &MultiSignalTest::emit); mTimer.start(); } } private: QTimer mTimer; }; class SimpleSignal: public QObject { Q_OBJECT public: void send(int id) { Q_EMIT messageReceived(id); } Q_SIGNAL void messageReceived(int id); QCoro::Task waitForMessage(int id) { QCORO_FOREACH(int msgId, qCoroSignalListener(this, &SimpleSignal::messageReceived)) { if (msgId == id) { co_return id; } } co_return -1; } }; class QCoroSignalTest : public QCoro::TestObject { Q_OBJECT private: QCoro::Task<> testTriggers_coro(QCoro::TestContext) { SignalTest obj; co_await qCoro(&obj, &SignalTest::voidSignal); static_assert( std::is_same_v>>); } QCoro::Task<> testReturnsValue_coro(QCoro::TestContext) { SignalTest obj; const auto result = co_await qCoro(&obj, &SignalTest::singleArg); static_assert(std::is_same_v); QCORO_COMPARE(result, QStringLiteral("YAY!")); } QCoro::Task<> testReturnsTuple_coro(QCoro::TestContext) { SignalTest obj; const auto result = co_await qCoro(&obj, &SignalTest::multiArg); static_assert(std::is_same_v>); const auto [value, number, ptr] = result; QCORO_COMPARE(value, QStringLiteral("YAY!")); QCORO_COMPARE(number, 42); QCORO_COMPARE(ptr, &obj); } QCoro::Task<> testTimeoutTriggersVoid_coro(QCoro::TestContext) { SignalTest obj; const auto result = co_await qCoro(&obj, &SignalTest::voidSignal, 10ms); static_assert(std::is_same_v>>); QCORO_VERIFY(!result.has_value()); } QCoro::Task<> testTimeoutVoid_coro(QCoro::TestContext) { SignalTest obj; const auto result = co_await qCoro(&obj, &SignalTest::voidSignal, 1s); static_assert(std::is_same_v>>); QCORO_VERIFY(result.has_value()); } QCoro::Task<> testTimeoutTriggersValue_coro(QCoro::TestContext) { SignalTest obj; const auto result = co_await qCoro(&obj, &SignalTest::singleArg, 10ms); static_assert(std::is_same_v>); QCORO_VERIFY(!result.has_value()); } QCoro::Task<> testTimeoutValue_coro(QCoro::TestContext) { SignalTest obj; const auto result = co_await qCoro(&obj, &SignalTest::singleArg, 1s); static_assert(std::is_same_v>); QCORO_VERIFY(result.has_value()); QCORO_COMPARE(*result, QStringLiteral("YAY!")); } QCoro::Task<> testTimeoutTriggersTuple_coro(QCoro::TestContext) { SignalTest obj; const auto result = co_await qCoro(&obj, &SignalTest::multiArg, 10ms); static_assert(std::is_same_v< decltype(result), const std::optional>>); QCORO_VERIFY(!result.has_value()); } QCoro::Task<> testTimeoutTuple_coro(QCoro::TestContext) { SignalTest obj; const auto result = co_await qCoro(&obj, &SignalTest::multiArg, 1s); static_assert(std::is_same_v< decltype(result), const std::optional>>); QCORO_VERIFY(result.has_value()); QCORO_COMPARE(std::get<0>(*result), QStringLiteral("YAY!")); QCORO_COMPARE(std::get<1>(*result), 42); QCORO_COMPARE(std::get<2>(*result), &obj); } void testThenTriggers_coro(TestLoop &el) { SignalTest obj; bool triggered = false; qCoro(&obj, &SignalTest::voidSignal).then([&]() { triggered = true; el.quit(); }); el.exec(); QVERIFY(triggered); } void testThenReturnsValue_coro(TestLoop &el) { SignalTest obj; std::optional value; qCoro(&obj, &SignalTest::singleArg).then([&](const QString &arg) { value = arg; el.quit(); }); el.exec(); QVERIFY(value.has_value()); QCOMPARE(*value, QStringLiteral("YAY!")); } void testThenReturnsTuple_coro(TestLoop &el) { SignalTest obj; std::optional str; std::optional num; std::optional ptr; qCoro(&obj, &SignalTest::multiArg).then([&](const std::tuple &args) { str = std::get<0>(args); num = std::get<1>(args); ptr = std::get<2>(args); el.quit(); }); el.exec(); QVERIFY(str.has_value()); QVERIFY(num.has_value()); QVERIFY(ptr.has_value()); QCOMPARE(*str, QStringLiteral("YAY!")); QCOMPARE(*num, 42); QCOMPARE(*ptr, &obj); } QCoro::Task<> testThenChained_coro(QCoro::TestContext) { SignalTest obj; const auto result = co_await qCoro(&obj, &SignalTest::singleArg).then([](const QString &arg) -> QCoro::Task { QTimer timer; timer.start(100ms); co_await timer; co_return arg + arg; }); QCORO_COMPARE(result, QStringLiteral("YAY!YAY!")); } QCoro::Task<> testVoidQPrivateSignal_coro(QCoro::TestContext) { SignalTest obj; const auto result = co_await qCoro(&obj, &SignalTest::privateVoid); static_assert(std::is_same_v>); Q_UNUSED(result); } QCoro::Task<> testSingleArgQPrivateSignal_coro(QCoro::TestContext) { SignalTest obj; const auto result = co_await qCoro(&obj, &SignalTest::privateSingleArg); static_assert(std::is_same_v); QCORO_COMPARE(result, QStringLiteral("YAY!")); } QCoro::Task<> testMultiArgQPrivateSignal_coro(QCoro::TestContext) { SignalTest obj; const auto [str, num, ptr] = co_await qCoro(&obj, &SignalTest::privateMultiArg); static_assert(std::is_same_v); static_assert(std::is_same_v); static_assert(std::is_same_v); QCORO_COMPARE(str, QStringLiteral("YAY!")); QCORO_COMPARE(num, 42); QCORO_COMPARE(ptr, &obj); } QCoro::Task<> testSignalListenerVoid_coro(QCoro::TestContext) { MultiSignalTest obj; auto generator = qCoroSignalListener(&obj, &MultiSignalTest::voidSignal); int count = 0; QCORO_FOREACH(const std::tuple<> &value, generator) { Q_UNUSED(value); if (++count == 10) { break; } } QCORO_COMPARE(count, 10); } QCoro::Task<> testSignalListenerValue_coro(QCoro::TestContext) { MultiSignalTest obj; auto generator = qCoroSignalListener(&obj, &MultiSignalTest::singleArg); int count = 0; QCORO_FOREACH(const QString &value, generator) { QCORO_COMPARE(value, QStringLiteral("YAY!")); if (++count == 10) { break; } } QCORO_COMPARE(count, 10); } QCoro::Task<> testSignalListenerTuple_coro(QCoro::TestContext) { MultiSignalTest obj; auto generator = qCoroSignalListener(&obj, &MultiSignalTest::multiArg); int count = 0; QCORO_FOREACH(const auto &value, generator) { QCORO_COMPARE(std::get<0>(value), QStringLiteral("YAY!")); QCORO_COMPARE(std::get<1>(value), 42); QCORO_COMPARE(std::get<2>(value), &obj); if (++count == 10) { break; } } QCORO_COMPARE(count, 10); } QCoro::Task<> testSignalListenerTimeout_coro(QCoro::TestContext) { QObject obj; // A signal that doesn't get invoked auto generator = qCoroSignalListener(&obj, &QObject::destroyed, 1ms); QCORO_FOREACH(const auto &value, generator) { Q_UNUSED(value); QCORO_FAIL("The signal should time out and the generator should not return invalid iterator."); } } QCoro::Task<> testSignalListenerQueue_coro(QCoro::TestContext ctx) { SignalTest test{false}; // I have a generator auto generator = qCoroSignalListener(&test, &SignalTest::voidSignal); // I emit signals that the generator is listening to, the generator // should enqueue them. for (int i = 0; i < 10; ++i) { test.emit(); } // I asynchronously wait for first iterator auto it = co_await generator.begin(); int count = 0; ctx.setShouldNotSuspend(); // I loop over generator - this should not suspend as we are simply consuming // events from the queue. for (; it != generator.end(); co_await ++it) { if (++count == 10) { break; } } QCORO_COMPARE(count, 10); } QCoro::Task<> testSignalAfterListenerQuits_coro(QCoro::TestContext) { SimpleSignal simple; auto msg1 = simple.waitForMessage(1); auto msg2 = simple.waitForMessage(2); simple.send(1); simple.send(2); QCORO_COMPARE(co_await msg1, 1); QCORO_COMPARE(co_await msg2, 2); } QCoro::Task<> testSignalListenerQPrivateSignalVoid_coro(QCoro::TestContext) { MultiSignalTest obj; auto generator = qCoroSignalListener(&obj, &MultiSignalTest::privateVoid); int count = 0; QCORO_FOREACH(const auto &value, generator) { static_assert(std::is_same_v &>); Q_UNUSED(value); if (++count == 10) { break; } } QCORO_COMPARE(count, 10); } QCoro::Task<> testSignalListenerQPrivateSignalValue_coro(QCoro::TestContext) { MultiSignalTest obj; auto generator = qCoroSignalListener(&obj, &MultiSignalTest::privateSingleArg); int count = 0; QCORO_FOREACH(const auto &value, generator) { static_assert(std::is_same_v); QCORO_COMPARE(value, QStringLiteral("YAY!")); if (++count == 10) { break; } } QCORO_COMPARE(count, 10); } QCoro::Task<> testSignalListenerQPrivateSignalTuple_coro(QCoro::TestContext) { MultiSignalTest obj; auto generator = qCoroSignalListener(&obj, &MultiSignalTest::privateMultiArg); int count = 0; QCORO_FOREACH(const auto &value, generator) { static_assert(std::is_same_v &>); QCORO_COMPARE(std::get(value), QStringLiteral("YAY!")); QCORO_COMPARE(std::get(value), 42); QCORO_COMPARE(std::get(value), &obj); if (++count == 10) { break; } } QCORO_COMPARE(count, 10); } QCoro::Task<> testSignalEmitterOnDifferentThread_coro(QCoro::TestContext) { SignalTest test; QThread thread; test.moveToThread(&thread); thread.start(); co_await qCoro(&test, &SignalTest::voidSignal); // Make sure we are resumed on our thread QCORO_COMPARE(QThread::currentThread(), qApp->thread()); co_await qCoro(&test, &SignalTest::signalThatsNeverEmitted, 20ms); // Make sure we are resumed on our thread after the timeout QCORO_COMPARE(QThread::currentThread(), qApp->thread()); thread.quit(); thread.wait(); } private Q_SLOTS: addTest(Triggers) addTest(ReturnsValue) addTest(ReturnsTuple) addTest(TimeoutVoid) addTest(TimeoutTriggersVoid) addTest(TimeoutValue) addTest(TimeoutTriggersValue) addTest(TimeoutTuple) addTest(TimeoutTriggersTuple) addTest(ThenChained) addTest(VoidQPrivateSignal) addTest(SingleArgQPrivateSignal) addTest(MultiArgQPrivateSignal) addThenTest(Triggers) addThenTest(ReturnsValue) addThenTest(ReturnsTuple) addTest(SignalListenerVoid) addTest(SignalListenerValue) addTest(SignalListenerTuple) addTest(SignalListenerTimeout) addTest(SignalListenerQueue) addTest(SignalAfterListenerQuits) addTest(SignalListenerQPrivateSignalVoid) addTest(SignalListenerQPrivateSignalValue) addTest(SignalListenerQPrivateSignalTuple) addTest(SignalEmitterOnDifferentThread) }; QTEST_GUILESS_MAIN(QCoroSignalTest) #include "qcorosignal.moc" qcoro-0.12.0/tests/qcorotask.cpp000066400000000000000000000525221477357142500166160ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "testobject.h" #include "qcorotask.h" #include "qcorotimer.h" #include "qcorosignal.h" #include #include #include #include #include #include #include using namespace std::chrono_literals; namespace { QCoro::Task<> timer(std::chrono::milliseconds timeout = 10ms) { QTimer timer; timer.setSingleShot(true); timer.start(timeout); co_await timer; } template QCoro::Task timerWithValue(T value, std::chrono::milliseconds timeout = 10ms) { co_await timer(timeout); co_return value; } auto thenScopeTestFunc(QEventLoop *el) { return timer().then([el]() { el->quit(); }); } template QCoro::Task thenScopeTestFuncWithValue(T value) { return timer().then([value]() { return value; }); } class ImplicitConversionBar { public: int number; }; class ImplicitConversionFoo { public: ImplicitConversionFoo(); ImplicitConversionFoo(ImplicitConversionBar bar) : string(QString::number(bar.number)) {} QString string; }; struct TestAwaitableBase { std::chrono::milliseconds delay() const { return mDelay; } private: std::chrono::milliseconds mDelay = 100ms; }; template struct TestAwaitable : TestAwaitableBase { public: TestAwaitable(T val) : mResult(val) {} bool await_ready() const { return false; } void await_suspend(std::coroutine_handle<> handle) { QTimer::singleShot(100ms, [handle = std::move(handle)]() { handle.resume(); }); } T await_resume() { return mResult; } private: T mResult; }; template<> struct TestAwaitable : TestAwaitableBase { public: bool await_ready() const { return false; } void await_suspend(std::coroutine_handle<> handle) { QTimer::singleShot(100ms, [handle = std::move(handle)]() { handle.resume(); }); } void await_resume() {} }; template struct TestAwaitableWithCoAwait { TestAwaitableWithCoAwait(T val) : mResult(val) {} TestAwaitable operator co_await() { return TestAwaitable(mResult); } private: T mResult; }; template<> struct TestAwaitableWithCoAwait { TestAwaitable operator co_await() { return TestAwaitable(); } }; } // namespace class QCoroTaskTest : public QCoro::TestObject { Q_OBJECT private: template void ignoreCoroutineResult(QEventLoop &el, Coro &&coro) { QTimer::singleShot(5s, &el, [&el]() mutable { el.exit(1); }); coro(); const int timeout = el.exec(); QCOMPARE(timeout, 0); } QCoro::Task<> testSimpleCoroutine_coro(QCoro::TestContext) { co_await timer(); } QCoro::Task<> testCoroutineValue_coro(QCoro::TestContext) { const auto coro = [](const QString &result) -> QCoro::Task { co_await timer(); co_return result; }; const auto value = QStringLiteral("Done!"); const auto result = co_await coro(value); QCORO_COMPARE(result, value); } QCoro::Task<> testCoroutineMoveValue_coro(QCoro::TestContext) { const auto coro = [](const QString &result) -> QCoro::Task> { co_await timer(); co_return std::make_unique(result); }; const auto value = QStringLiteral("Done ptr!"); const auto result = co_await coro(value); QCORO_COMPARE(*result.get(), value); } QCoro::Task<> testSyncCoroutine_coro(QCoro::TestContext context) { context.setShouldNotSuspend(); const auto coro = []() -> QCoro::Task { co_return 42; }; const auto result = co_await coro(); QCORO_COMPARE(result, 42); } QCoro::Task<> testCoroutineWithException_coro(QCoro::TestContext) { const auto coro = []() -> QCoro::Task { co_await timer(); throw std::runtime_error("Invalid result"); co_return 42; }; try { const auto result = co_await coro(); QCORO_FAIL("Exception was not propagated."); Q_UNUSED(result); } catch (const std::runtime_error &) { // OK } catch (...) { QCORO_FAIL("Exception type was not propagated, or other exception was thrown."); } } QCoro::Task<> testVoidCoroutineWithException_coro(QCoro::TestContext) { const auto coro = []() -> QCoro::Task<> { co_await timer(); throw std::runtime_error("Error"); }; try { co_await coro(); QCORO_FAIL("Exception was not propagated."); } catch (const std::runtime_error &) { // OK } catch (...) { QCORO_FAIL("Exception type was not propagated, or other exception was thrown."); } } QCoro::Task<> testCoroutineFrameDestroyed_coro(QCoro::TestContext) { bool destroyed = false; const auto coro = [&destroyed]() -> QCoro::Task<> { const auto guard = qScopeGuard([&destroyed]() mutable { destroyed = true; }); QCORO_VERIFY(!destroyed); co_await timer(); QCORO_VERIFY(!destroyed); }; co_await coro(); QCORO_VERIFY(destroyed); } QCoro::Task<> testExceptionPropagation_coro(QCoro::TestContext) { QCORO_VERIFY_EXCEPTION_THROWN( co_await []() -> QCoro::Task { throw std::runtime_error("Test!"); co_return 42; }(), std::runtime_error); QCORO_VERIFY_EXCEPTION_THROWN( co_await []() -> QCoro::Task<> { throw std::runtime_error("Test!"); }(), std::runtime_error); QCORO_VERIFY_EXCEPTION_THROWN( co_await []() -> QCoro::Task { co_await timer(); throw std::runtime_error("Test!"); co_return 42; }(), std::runtime_error); QCORO_VERIFY_EXCEPTION_THROWN( co_await []() -> QCoro::Task<> { co_await timer(); throw std::runtime_error("Test!"); }(), std::runtime_error); } QCoro::Task<> testThenReturnValueNoArgument_coro(QCoro::TestContext) { auto task = timer().then([]() { return 42; }); static_assert(std::is_same_v>); const auto result = co_await task; QCORO_COMPARE(result, 42); } QCoro::Task<> testThenReturnValueWithArgument_coro(QCoro::TestContext) { auto task = timerWithValue(42).then([](int param) -> QCoro::Task { co_return param * 2; }); static_assert(std::is_same_v>); const auto result = co_await task; QCORO_COMPARE(result, 84); } QCoro::Task<> testThenReturnTaskVoidNoArgument_coro(QCoro::TestContext) { auto task = timer().then([]() -> QCoro::Task { co_await timer(); }); static_assert(std::is_same_v>); co_await task; } QCoro::Task<> testThenReturnTaskVoidWithArgument_coro(QCoro::TestContext) { auto task = timerWithValue(42).then([](int result) -> QCoro::Task { co_await timer(); Q_UNUSED(result); }); static_assert(std::is_same_v>); co_await task; } QCoro::Task<> testThenReturnTaskTNoArgument_coro(QCoro::TestContext) { auto task = timer().then([]() -> QCoro::Task { co_await timer(); co_return 42; }); static_assert(std::is_same_v>); const auto result = co_await task; QCORO_COMPARE(result, 42); } QCoro::Task<> testThenReturnTaskTWithArgument_coro(QCoro::TestContext) { auto task = timerWithValue(42).then([](int val) -> QCoro::Task { co_await timer(); co_return val * 2; }); static_assert(std::is_same_v>); const auto result = co_await task; QCORO_COMPARE(result, 84); } QCoro::Task<> testThenReturnValueSync_coro(QCoro::TestContext context) { context.setShouldNotSuspend(); auto task = []() -> QCoro::Task { co_return 42; }().then([](int param) { return param * 2; }); const int result = co_await task; QCORO_COMPARE(result, 84); } QCoro::Task<> testThenScopeAwait_coro(QCoro::TestContext) { const int result = co_await thenScopeTestFuncWithValue(42); QCORO_COMPARE(result, 42); } QCoro::Task<> testThenExceptionPropagation_coro(QCoro::TestContext) { QCORO_VERIFY_EXCEPTION_THROWN( co_await []() -> QCoro::Task { co_await timer(); throw std::runtime_error("Test!"); co_return 42; }().then([](int) -> QCoro::Task<> { QCORO_FAIL("The then() callback should never be called"); co_return; }), std::runtime_error); } QCoro::Task<> testThenError_coro(QCoro::TestContext) { bool exceptionThrown = false; co_await []() -> QCoro::Task { co_await timer(); throw std::runtime_error("Test!"); co_return 42; }().then([](int) -> QCoro::Task<> { QCORO_FAIL("The then() callback should not be called"); }, [&exceptionThrown](const std::exception &) { exceptionThrown = true; } ); QCORO_VERIFY(exceptionThrown); } QCoro::Task<> testThenErrorWithValue_coro(QCoro::TestContext) { bool exceptionThrown = false; bool thenCalled = false; const int result = co_await []() -> QCoro::Task<> { co_await timer(); throw std::runtime_error("Test!"); }().then([&thenCalled]() -> QCoro::Task { thenCalled = true; co_return 42; }, [&exceptionThrown](const std::exception &) { exceptionThrown = true; } ); // We handled an exception, so there's no error and it should // be default-constructed. QCORO_COMPARE(result, 0); QCORO_VERIFY(!thenCalled); QCORO_VERIFY(exceptionThrown); } void testThenImplicitArgumentConversion_coro(TestLoop &el) { QTimer test; QString result; qCoro(test).waitForTimeout().then([]() -> QCoro::Task { ImplicitConversionBar bar{42}; co_await timer(10ms); co_return bar; }).then([&](ImplicitConversionFoo foo) { result = foo.string; el.quit(); }); test.start(10ms); el.exec(); QCOMPARE(result, QStringLiteral("42")); } void testReturnValueImplicitConversion(QCoro::TestContext) { const auto testcoro [[maybe_unused]] = []() -> QCoro::Task { co_return 42LL; }; } QCoro::Task<> testMultipleAwaiters_coro(QCoro::TestContext) { auto task = timer(100ms); bool called = false; // Internally co_awaits task task.then([&called]() { called = true; }); co_await task; QCORO_VERIFY(called); } QCoro::Task<> testMultipleAwaitersSync_coro(QCoro::TestContext ctx) { ctx.setShouldNotSuspend(); auto task = []() -> QCoro::Task<> { co_return; }(); bool called = false; task.then([&called]() { called = true; }); co_await task; QCORO_VERIFY(called); } Q_SIGNAL void callbackCalled(); template QCoro::Task<> verifySignalEmitted(QObjectDerived *context, Signal &&signal) { bool called = false; co_await qCoro(context, std::move(signal)).then([&]() { called = true; }); QCORO_VERIFY(called); } QCoro::Task<> testTaskConnect_coro(QCoro::TestContext) { // Test that free functions can be passed as callback QCoro::connect(timer(), this, [this]() { Q_EMIT callbackCalled(); }); co_await verifySignalEmitted(this, &QCoroTaskTest::callbackCalled); // Check that member functions can be passed as callback QCoro::connect(timer(), this, &QCoroTaskTest::callbackCalled); co_await verifySignalEmitted(this, &QCoroTaskTest::callbackCalled); // Test that the code still compiles if the value of the coroutine is not used by the function. auto nonVoidCoroutine = []() -> QCoro::Task { co_await timer(); co_return QStringLiteral("Hello World!"); }; QCoro::connect(nonVoidCoroutine(), this, [this]() { Q_EMIT callbackCalled(); }); co_await verifySignalEmitted(this, &QCoroTaskTest::callbackCalled); QCoro::connect(nonVoidCoroutine(), this, [this](QString) { Q_EMIT callbackCalled(); }); co_await verifySignalEmitted(this, &QCoroTaskTest::callbackCalled); } private Q_SLOTS: addTest(SimpleCoroutine) addTest(CoroutineValue) addTest(CoroutineMoveValue) addTest(SyncCoroutine) addTest(CoroutineWithException) addTest(VoidCoroutineWithException) addTest(CoroutineFrameDestroyed) addTest(ExceptionPropagation) addTest(ThenReturnValueNoArgument) addTest(ThenReturnValueWithArgument) addTest(ThenReturnTaskVoidNoArgument) addTest(ThenReturnTaskVoidWithArgument) addTest(ThenReturnTaskTNoArgument) addTest(ThenReturnTaskTWithArgument) addTest(ThenReturnValueSync) addTest(ThenScopeAwait) addTest(ThenExceptionPropagation) addTest(ThenError) addTest(ThenErrorWithValue) addTest(TaskConnect) addThenTest(ImplicitArgumentConversion) addTest(MultipleAwaiters) addTest(MultipleAwaitersSync) // See https://github.com/danvratil/qcoro/issues/24 void testEarlyReturn() { QEventLoop loop; const auto testReturn = [](bool immediate) -> QCoro::Task { if (immediate) { co_return true; } else { co_await timer(); co_return true; } }; bool immediateResult = false; bool delayedResult = false; const auto testImmediate = [&]() -> QCoro::Task<> { immediateResult = co_await testReturn(true); }; const auto testDelayed = [&]() -> QCoro::Task<> { delayedResult = co_await testReturn(false); loop.quit(); }; QMetaObject::invokeMethod( &loop, [&]() { testImmediate(); }, Qt::QueuedConnection); QMetaObject::invokeMethod( &loop, [&]() { testDelayed(); }, Qt::QueuedConnection); loop.exec(); QVERIFY(immediateResult); QVERIFY(delayedResult); } // TODO: Test timeout void testWaitFor() { QCoro::waitFor(timer()); } // TODO: Test timeout void testWaitForWithValue() { const auto result = QCoro::waitFor([]() -> QCoro::Task { co_await timer(); co_return 42; }()); QCOMPARE(result, 42); } void testEarlyReturnWaitFor() { QCoro::waitFor([]() -> QCoro::Task<> { co_return; }()); } void testEarlyReturnWaitForWithValue() { const auto result = QCoro::waitFor([]() -> QCoro::Task { co_return 42; }()); QCOMPARE(result, 42); } void testWaitForAwaitable() { TestAwaitable awaitable(42); QElapsedTimer timer; timer.start(); static_assert(std::is_same_v); const int result = QCoro::waitFor(awaitable); QCOMPARE(result, 42); QVERIFY(timer.elapsed() >= static_cast(awaitable.delay().count()) * 0.9); } void testWaitForVoidAwaitable() { TestAwaitable awaitable; QElapsedTimer timer; timer.start(); static_assert(std::is_void_v); QCoro::waitFor(awaitable); QVERIFY(timer.elapsed() >= static_cast(awaitable.delay().count()) * 0.9); } void testWaitForAwaitableWithOperatorCoAwait() { TestAwaitableWithCoAwait awaitable(42); QCoro::waitFor(awaitable); QElapsedTimer timer; timer.start(); static_assert(std::is_same_v); const int result = QCoro::waitFor(awaitable); QCOMPARE(result, 42); QVERIFY(timer.elapsed() >= (90ms).count()); } void testWaitForVoidAwaitableWithOperatorCoAwait() { TestAwaitableWithCoAwait awaitable; QElapsedTimer timer; timer.start(); static_assert(std::is_void_v); QCoro::waitFor(awaitable); QVERIFY(timer.elapsed() >= (90ms).count()); } void testWaitForWithValueRethrowsException() { const auto coro = []() -> QCoro::Task { co_await timer(); throw std::runtime_error("Exception"); co_return 42; }; #if QT_VERSION >= QT_VERSION_CHECK(6, 3, 0) QVERIFY_THROWS_EXCEPTION(std::runtime_error, QCoro::waitFor(coro())); #else QVERIFY_EXCEPTION_THROWN(QCoro::waitFor(coro()), std::runtime_error); #endif } void testWaitForRethrowsException() { const auto coro = []() -> QCoro::Task<> { co_await timer(); throw std::runtime_error("Exception"); }; #if QT_VERSION >= QT_VERSION_CHECK(6, 3, 0) QVERIFY_THROWS_EXCEPTION(std::runtime_error, QCoro::waitFor(coro())); #else QVERIFY_EXCEPTION_THROWN(QCoro::waitFor(coro()), std::runtime_error); #endif } void testIgnoredVoidTaskResult() { QEventLoop el; ignoreCoroutineResult(el, [&el]() -> QCoro::Task<> { co_await timer(); el.quit(); }); } void testIgnoredValueTaskResult() { QEventLoop el; ignoreCoroutineResult(el, [&el]() -> QCoro::Task { co_await timer(); el.quit(); co_return QStringLiteral("Result"); }); } void testThenVoidNoArgument() { QEventLoop el; { timer().then([&el]() { el.quit(); }); } el.exec(); } void testThenDiscardsReturnValue() { QEventLoop el; bool called = false; timerWithValue(42).then([&]() { el.quit(); called = true; }); el.exec(); QVERIFY(called); } void testThenScope() { QEventLoop el; thenScopeTestFunc(&el); el.exec(); } void testThenVoidWithArgument() { QEventLoop el; int result = 0; { timerWithValue(42).then([&el, &result](int val) { result = val; el.quit(); }); } el.exec(); QCOMPARE(result, 42); } void testThenVoidWithFunction() { QEventLoop el; timerWithValue(10ms).then(timer).then([&el]() { el.quit(); }); el.exec(); } void testThenErrorInCallback() { QEventLoop el; QTimer::singleShot(5s, &el, [&el]() { el.quit(); QFAIL("Timeout waiting for coroutine"); }); []() -> QCoro::Task<> { co_await timer(); }().then([]() { throw std::runtime_error("Test!"); }, [](const std::exception &) { QFAIL("Continuation exception should not be handled by the same error handled"); }).then([]() { QFAIL("Second then continuation should not be called."); }, [&el](const std::exception &) { el.quit(); }); el.exec(); } void testThenExceptionInError() { QEventLoop el; QTimer::singleShot(5s, &el, [&el]() { el.quit(); QFAIL("Timeout waiting for coroutine"); }); []() -> QCoro::Task<> { co_await timer(); throw std::runtime_error("Test!"); }().then([]() { QFAIL("The then() continuation should not be called"); }, [](const std::exception &) { throw std::runtime_error("Another test!"); }).then([]() { QFAIL("Second then() continuation should not be called"); }, [&el](const std::exception &) { el.quit(); }); el.exec(); } void testTaskConnectContext_coro() { auto task = timer(200ms); static_assert(std::is_same_v>); bool called = false; auto context = new QObject(); QCoro::connect(task, context, [&]() { called = true; }); // Delete context, callback should not be called delete context; QCoro::waitFor(task); QVERIFY(!called); } }; QTEST_GUILESS_MAIN(QCoroTaskTest) #include "qcorotask.moc" qcoro-0.12.0/tests/qcorotcpserver.cpp000066400000000000000000000073021477357142500176650ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "testobject.h" #include "qcoro/network/qcorotcpserver.h" #include "qcoro/network/qcoroabstractsocket.h" #include #include #include #include using namespace std::chrono_literals; class Client { public: Client(uint16_t serverPort, std::mutex &mutex, bool &ok) : mThread([serverPort, &mutex, &ok]() mutable { std::this_thread::sleep_for(500ms); std::lock_guard lock{mutex}; QTcpSocket socket; socket.connectToHost(QHostAddress::LocalHost, serverPort); if (!socket.waitForConnected(10'000)) { qWarning() << "Not connected within timeout" << socket.errorString(); ok = false; return; } socket.write("Hello World!"); socket.flush(); socket.close(); ok = true; }) {} ~Client() { mThread.join(); } private: std::thread mThread; }; class QCoroTcpServerTest: public QCoro::TestObject { Q_OBJECT private: QCoro::Task<> testWaitForNewConnectionTriggers_coro(QCoro::TestContext) { QTcpServer server; QCORO_VERIFY(server.listen(QHostAddress::LocalHost)); QCORO_VERIFY(server.isListening()); const quint16 serverPort = server.serverPort(); std::mutex mutex; bool ok = false; Client client(serverPort, mutex, ok); auto *connection = co_await qCoro(server).waitForNewConnection(10s); QCORO_VERIFY(connection != nullptr); const auto data = co_await qCoro(connection).readAll(); QCORO_COMPARE(data, QByteArray{"Hello World!"}); std::lock_guard lock{mutex}; QCORO_VERIFY(ok); } void testThenWaitForNewConnectionTriggers_coro(TestLoop &el) { QTcpServer server; QVERIFY(server.listen(QHostAddress::LocalHost)); const quint16 serverPort = server.serverPort(); std::mutex mutex; bool ok = false; Client client(serverPort, mutex, ok); bool called = false; qCoro(server).waitForNewConnection(10s).then([&](QTcpSocket *socket) -> QCoro::Task { called = true; if (!socket) { el.quit(); co_return; } const auto data = co_await qCoro(socket).readAll(); QCORO_COMPARE(data, QByteArray("Hello World!")); el.quit(); }); el.exec(); std::lock_guard lock{mutex}; QVERIFY(called); QVERIFY(ok); } QCoro::Task<> testDoesntCoAwaitPendingConnection_coro(QCoro::TestContext testContext) { testContext.setShouldNotSuspend(); QTcpServer server; QCORO_VERIFY(server.listen(QHostAddress::LocalHost)); const int serverPort = server.serverPort(); bool ok = false; std::mutex mutex; Client client(serverPort, mutex, ok); QCORO_VERIFY(server.waitForNewConnection(10'000)); auto *connection = co_await qCoro(server).waitForNewConnection(10s); connection->waitForReadyRead(); // can't use coroutine, it might suspend or not, depending on how eventloop // gets triggered, which fails the test since it's setShouldNotSuspend() QCORO_COMPARE(connection->readAll(), QByteArray{"Hello World!"}); std::lock_guard lock{mutex}; QCORO_VERIFY(ok); } private Q_SLOTS: addCoroAndThenTests(WaitForNewConnectionTriggers) addTest(DoesntCoAwaitPendingConnection) }; QTEST_GUILESS_MAIN(QCoroTcpServerTest) #include "qcorotcpserver.moc" qcoro-0.12.0/tests/qcorothread.cpp000066400000000000000000000037361477357142500171260ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "testobject.h" #include "qcoro/core/qcorothread.h" #include "qcoro/core/qcorosignal.h" #include #include #include #include using namespace std::chrono_literals; class QCoroThreadTest : public QCoro::TestObject { Q_OBJECT private: QCoro::Task<> testWaitForStarted_coro(QCoro::TestContext) { std::unique_ptr thread(QThread::create([]() { std::this_thread::sleep_for(100ms); })); const auto threadGuard = qScopeGuard([&]() { thread->wait(); }); QCORO_DELAY(thread->start()); const bool ok = co_await qCoro(thread.get()).waitForStarted(); QCORO_VERIFY(thread->isRunning()); QCORO_VERIFY(ok); } QCoro::Task<> testWaitForFinished_coro(QCoro::TestContext) { std::unique_ptr thread(QThread::create([]() { std::this_thread::sleep_for(100ms); })); thread->start(); co_await qCoro(thread.get()).waitForStarted(); QCORO_VERIFY(thread->isRunning()); const bool ok = co_await qCoro(thread.get()).waitForFinished(); QCORO_VERIFY(thread->isFinished()); QCORO_VERIFY(ok); } QCoro::Task<> testMoveToThread_coro(QCoro::TestContext) { QThread newThread; newThread.start(); QCORO_COMPARE(QThread::currentThread(), QCoreApplication::instance()->thread()); co_await QCoro::moveToThread(&newThread); QCORO_COMPARE(QThread::currentThread(), &newThread); co_await QCoro::moveToThread(qApp->thread()); QCORO_COMPARE(QThread::currentThread(), QCoreApplication::instance()->thread()); newThread.exit(); newThread.wait(); } private Q_SLOTS: addTest(WaitForStarted) addTest(WaitForFinished) addTest(MoveToThread) }; QTEST_GUILESS_MAIN(QCoroThreadTest) #include "qcorothread.moc" qcoro-0.12.0/tests/qcorowaitfor.cpp000066400000000000000000000027551477357142500173320ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Joey Richey // // SPDX-License-Identifier: MIT #include "testobject.h" #include "qcorotimer.h" #include class QCoroWaitForTest : public QCoro::TestObject { Q_OBJECT private: QCoro::Task testPrimitiveType_coro(QCoro::TestContext ctx) { ctx.setShouldNotSuspend(); auto task_test = []() -> QCoro::Task { co_return 7; }; const int ret = QCoro::waitFor(task_test()); QCORO_VERIFY(ret == 7); } QCoro::Task testDefaultConstructible_coro(QCoro::TestContext ctx) { ctx.setShouldNotSuspend(); auto task_test = []() -> QCoro::Task { co_return "seven"; }; const std::string ret = QCoro::waitFor(task_test()); QCORO_VERIFY(ret == "seven"); } QCoro::Task testNonDefaultConstructible_coro(QCoro::TestContext ctx) { ctx.setShouldNotSuspend(); struct test_struct { explicit test_struct(int i_) : i(i_) {} int i; }; auto task_test = []() -> QCoro::Task { co_return test_struct(7); }; const test_struct ret = QCoro::waitFor(task_test()); QCORO_VERIFY(ret.i == 7); } private Q_SLOTS: addTest(PrimitiveType) addTest(DefaultConstructible) addTest(NonDefaultConstructible) }; QTEST_GUILESS_MAIN(QCoroWaitForTest) #include "qcorowaitfor.moc" qcoro-0.12.0/tests/qcorowebsocket.cpp000066400000000000000000000232431477357142500176400ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "testobject.h" #include "testwsserver.h" #include "qcoro/websockets/qcorowebsocket.h" #include class QCoroWebSocketTest : public QCoro::TestObject { Q_OBJECT public: explicit QCoroWebSocketTest(QObject *parent = nullptr) : QCoro::TestObject(parent) { // On Windows, constructing QWebSocket for the first time takes some time // (most likely due to loading OpenSSL), which causes the first test to // time out on the CI. QWebSocket socket; } private: template QCoro::Task<> testReceived(const T &msg, SendFunc sendFunc, RecvFunc recvFunc) { QWebSocket socket; QCORO_VERIFY(connectSocket(socket)); QCORO_DELAY(std::invoke(sendFunc, socket, msg)); auto coroSocket = qCoro(socket); auto gen = std::invoke(recvFunc, &coroSocket, std::chrono::milliseconds{-1}); const auto data = co_await gen.begin(); QCORO_VERIFY(data != gen.end()); if constexpr (std::is_same_v, QString> || std::is_same_v, QByteArray>) { QCORO_COMPARE(*data, msg); } else { QCORO_COMPARE(std::get<0>(*data), msg); QCORO_COMPARE(std::get<1>(*data), true); } } template QCoro::Task<> testTimeout(RecvFunc recvFunc) { mServer.setExpectTimeout(); QWebSocket socket; QCORO_VERIFY(connectSocket(socket)); auto coroSocket = qCoro(socket); auto gen = std::invoke(recvFunc, &coroSocket, 10ms); const auto data = co_await gen.begin(); QCORO_COMPARE(data, gen.end()); } template QCoro::Task<> testGeneratorEndOnSocketClose(RecvFunc recvFunc) { mServer.setExpectTimeout(); QWebSocket socket; QCORO_VERIFY(connectSocket(socket)); QCORO_DELAY(socket.close()); auto coroSocket = qCoro(socket); auto gen = std::invoke(recvFunc, &coroSocket, std::chrono::milliseconds{-1}); const auto it = co_await gen.begin(); QCORO_COMPARE(it, gen.end()); } private: QCoro::Task<> testWaitForOpenWithUrl_coro(QCoro::TestContext) { QWebSocket socket; const auto result = co_await qCoro(socket).open(mServer.url()); QCORO_VERIFY(result); QCORO_COMPARE(socket.state(), QAbstractSocket::ConnectedState); QCORO_VERIFY(mServer.waitForConnection()); } void testThenWaitForOpenWithUrl_coro(TestLoop &el) { QWebSocket socket; bool called = false; qCoro(socket).open(mServer.url()).then([&el, &called](bool connected) { called = true; el.quit(); QVERIFY(connected); }); el.exec(); QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); QVERIFY(called); QVERIFY(mServer.waitForConnection()); } QCoro::Task<> testTimeoutOpenWithUrl_coro(QCoro::TestContext) { QWebSocket socket; const auto url = mServer.url(); mServer.stop(); // stop the server so we cannot connect const auto result = co_await qCoro(socket).open(url, 10ms); QCORO_VERIFY(!result); } void testThenTimeoutOpenWithUrl_coro(TestLoop &el) { QWebSocket socket; const auto url = mServer.url(); mServer.stop(); bool called = false; qCoro(socket).open(url, 10ms).then([&el, &called](bool connected) { el.quit(); called = true; QVERIFY(!connected); }); el.exec(); QVERIFY(called); } QCoro::Task<> testWaitForOpenWithNetworkRequest_coro(QCoro::TestContext) { QWebSocket socket; QNetworkRequest request(mServer.url()); const auto result = co_await qCoro(socket).open(request); QCORO_VERIFY(result); QCORO_COMPARE(socket.state(), QAbstractSocket::ConnectedState); QCORO_VERIFY(mServer.waitForConnection()); } void testThenWaitForOpenWithNetworkRequest_coro(TestLoop &el) { QWebSocket socket; bool called = false; qCoro(socket).open(QNetworkRequest{mServer.url()}).then([&el, &called](bool connected) { called = true; el.quit(); QVERIFY(connected); }); el.exec(); QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); QVERIFY(called); QVERIFY(mServer.waitForConnection()); } QCoro::Task<> testDoesntCoawaitOpenedSocket_coro(QCoro::TestContext ctx) { QWebSocket socket; QCORO_VERIFY(connectSocket(socket)); ctx.setShouldNotSuspend(); QCORO_COMPARE(socket.state(), QAbstractSocket::ConnectedState); const auto connected = co_await qCoro(socket).open(mServer.url()); QCORO_VERIFY(connected); } QCoro::Task<> testPing_coro(QCoro::TestContext) { QWebSocket socket; QCORO_VERIFY(connectSocket(socket)); const auto response = co_await qCoro(socket).ping("PING!"); QCORO_VERIFY(response.has_value()); QCORO_VERIFY(*response >= 0ms); // the latency will be somewhere around 0 } void testThenPing_coro(TestLoop &el) { QWebSocket socket; QVERIFY(connectSocket(socket)); bool called = false; qCoro(socket).ping("PING!").then([&el, &called](std::optional pong) { el.quit(); called = true; QVERIFY(pong.has_value()); QVERIFY(*pong >= 0ms); }); el.exec(); QVERIFY(called); } QCoro::Task<> testBinaryFrame_coro(QCoro::TestContext) { co_await testReceived(QByteArray("TEST MESSAGE"), &QWebSocket::sendBinaryMessage, &QCoro::detail::QCoroWebSocket::binaryFrames); } QCoro::Task<> testBinaryFrameTimeout_coro(QCoro::TestContext) { co_await testTimeout(&QCoro::detail::QCoroWebSocket::binaryFrames); } QCoro::Task<> testBinaryFrameGeneratorEndsOnSocketClose_coro(QCoro::TestContext) { co_await testGeneratorEndOnSocketClose(&QCoro::detail::QCoroWebSocket::binaryFrames); } QCoro::Task<> testBinaryMessage_coro(QCoro::TestContext) { co_await testReceived(QByteArray("TEST MESSAGE"), &QWebSocket::sendBinaryMessage, &QCoro::detail::QCoroWebSocket::binaryMessages); } QCoro::Task<> testBinaryMessageTimeout_coro(QCoro::TestContext) { co_await testTimeout(&QCoro::detail::QCoroWebSocket::binaryMessages); } QCoro::Task<> testBinaryMessageGeneratorEndsOnSocketClose_coro(QCoro::TestContext) { co_await testGeneratorEndOnSocketClose(&QCoro::detail::QCoroWebSocket::binaryMessages); } QCoro::Task<> testTextFrame_coro(QCoro::TestContext) { co_await testReceived(QStringLiteral("TEST MESSAGE"), &QWebSocket::sendTextMessage, &QCoro::detail::QCoroWebSocket::textFrames); } QCoro::Task<> testTextFrameTimeout_coro(QCoro::TestContext) { co_await testTimeout(&QCoro::detail::QCoroWebSocket::textFrames); } QCoro::Task<> testTextFrameGeneratorEndsOnSocketClose_coro(QCoro::TestContext) { co_await testGeneratorEndOnSocketClose(&QCoro::detail::QCoroWebSocket::textFrames); } QCoro::Task<> testTextMessage_coro(QCoro::TestContext) { co_await testReceived(QStringLiteral("TEST MESSAGE"), &QWebSocket::sendTextMessage, &QCoro::detail::QCoroWebSocket::textMessages); } QCoro::Task<> testTextMessageTimeout_coro(QCoro::TestContext) { co_await testTimeout(&QCoro::detail::QCoroWebSocket::textMessages); } QCoro::Task<> testTextMessageGeneratorEndsOnSocketClose_coro(QCoro::TestContext) { co_await testGeneratorEndOnSocketClose(&QCoro::detail::QCoroWebSocket::textMessages); } QCoro::Task<> testReadFragmentedMessage_coro(QCoro::TestContext) { QWebSocket socket; QUrl url = mServer.url(); url.setPath(QStringLiteral("/large")); QCORO_VERIFY(QCoro::waitFor(qCoro(socket).open(url))); QCORO_DELAY(socket.sendBinaryMessage("One large, please")); auto frames = qCoro(socket).binaryFrames(); QByteArray data; for (auto frame = co_await frames.begin(), end = frames.end(); frame != end; co_await ++frame) { data += std::get<0>(*frame); if (std::get<1>(*frame)) { // last break; } } QCORO_VERIFY(data.size() >= 10 * 1024 * 1024); // 10MB } private Q_SLOTS: void init() { mServer.start(); } void cleanup() { mServer.stop(); } addCoroAndThenTests(WaitForOpenWithUrl) addCoroAndThenTests(TimeoutOpenWithUrl) addCoroAndThenTests(WaitForOpenWithNetworkRequest) addTest(DoesntCoawaitOpenedSocket) addCoroAndThenTests(Ping) addTest(BinaryFrame) addTest(BinaryFrameTimeout) addTest(BinaryFrameGeneratorEndsOnSocketClose) addTest(BinaryMessage) addTest(BinaryMessageTimeout) addTest(BinaryMessageGeneratorEndsOnSocketClose) addTest(TextFrame) addTest(TextFrameTimeout) addTest(TextFrameGeneratorEndsOnSocketClose) addTest(TextMessage) addTest(TextMessageTimeout) addTest(TextMessageGeneratorEndsOnSocketClose) addTest(ReadFragmentedMessage) private: bool connectSocket(QWebSocket &socket) { return QCoro::waitFor(qCoro(socket).open(mServer.url())); } TestWsServer mServer; }; QTEST_GUILESS_MAIN(QCoroWebSocketTest) #include "qcorowebsocket.moc" qcoro-0.12.0/tests/qcorowebsocketserver.cpp000066400000000000000000000117141477357142500210670ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "websockets/qcorowebsocketserver.h" #include "websockets/qcorowebsocket.h" #include "testobject.h" #include #include #include class QCoroWebSocketServerTest : public QCoro::TestObject { Q_OBJECT public: QCoroWebSocketServerTest(QObject *parent = nullptr) : QCoro::TestObject(parent) { // On Windows, constructing QWebSocket for the first time takes some time // (most likely due to loading OpenSSL), which causes the first test to // time out on the CI. QWebSocket socket; } private: QCoro::Task<> testNextPendingConnection_coro(QCoro::TestContext) { QWebSocketServer server(QStringLiteral("TestWSServer"), QWebSocketServer::NonSecureMode); QCORO_VERIFY(server.listen(QHostAddress::LocalHost)); QWebSocket socket; QCORO_DELAY(socket.open(server.serverUrl())); const auto serverSocket = std::unique_ptr(co_await qCoro(server).nextPendingConnection()); QCORO_VERIFY(serverSocket != nullptr); } void testThenNextPendingConnection_coro(TestLoop &el) { QWebSocketServer server(QStringLiteral("TestWSServer"), QWebSocketServer::NonSecureMode); QVERIFY(server.listen(QHostAddress::LocalHost)); QWebSocket socket; QCORO_DELAY(socket.open(server.serverUrl())); bool called = false; qCoro(server).nextPendingConnection().then([&el, &called](QWebSocket *socket) { el.quit(); called = true; QVERIFY(socket != nullptr); socket->deleteLater(); }); el.exec(); QVERIFY(called); } QCoro::Task<> testNextPendingConnectionTimeout_coro(QCoro::TestContext) { QWebSocketServer server(QStringLiteral("TestWSServer"), QWebSocketServer::NonSecureMode); QCORO_VERIFY(server.listen(QHostAddress::LocalHost)); const auto *socket = co_await qCoro(server).nextPendingConnection(10ms); QCORO_COMPARE(socket, nullptr); } void testThenNextPendingConnectionTimeout_coro(TestLoop &el) { QWebSocketServer server(QStringLiteral("TestWSServer"), QWebSocketServer::NonSecureMode); QVERIFY(server.listen(QHostAddress::LocalHost)); bool called = false; qCoro(server).nextPendingConnection(100ms).then([&el, &called](QWebSocket *socket) { el.quit(); called = true; QCOMPARE(socket, nullptr); }); el.exec(); QVERIFY(called); } QCoro::Task<> testClosingServerResumesAwaiters_coro(QCoro::TestContext) { QWebSocketServer server(QStringLiteral("TestWSServer"), QWebSocketServer::NonSecureMode); QCORO_VERIFY(server.listen(QHostAddress::LocalHost)); QCORO_DELAY(server.close()); auto *socket = co_await qCoro(server).nextPendingConnection(); QCORO_COMPARE(socket, nullptr); } void testThenClosingServerResumesAwaiters_coro(TestLoop &el) { QWebSocketServer server(QStringLiteral("TestWSServer"), QWebSocketServer::NonSecureMode); QVERIFY(server.listen(QHostAddress::LocalHost)); QCORO_DELAY(server.close()); bool called = false; qCoro(server).nextPendingConnection().then([&el, &called](QWebSocket *socket) { el.quit(); called = true; QCOMPARE(socket, nullptr); }); el.exec(); QVERIFY(called); } QCoro::Task<> testDoesntCoawaitNonlisteningServer_coro(QCoro::TestContext ctx) { ctx.setShouldNotSuspend(); QWebSocketServer server(QStringLiteral("TestWSServer"), QWebSocketServer::NonSecureMode); const auto *socket = co_await qCoro(server).nextPendingConnection(); QCORO_COMPARE(socket, nullptr); } QCoro::Task<> testDoesntCoawaitWithPendingConnection_coro(QCoro::TestContext ctx) { ctx.setShouldNotSuspend(); QWebSocketServer server(QStringLiteral("TestWSServer"), QWebSocketServer::NonSecureMode); QCORO_VERIFY(server.listen(QHostAddress::LocalHost)); QWebSocket socket; QCORO_VERIFY(QCoro::waitFor(qCoro(socket).open(server.serverUrl()))); QCORO_COMPARE(socket.state(), QAbstractSocket::ConnectedState); QTest::qWait(100); // give the server time to register the incoming connection QCORO_VERIFY(server.hasPendingConnections()); const auto serverSocket = std::unique_ptr(co_await qCoro(server).nextPendingConnection()); QCORO_VERIFY(serverSocket); } private Q_SLOTS: addCoroAndThenTests(NextPendingConnection) addCoroAndThenTests(NextPendingConnectionTimeout) addCoroAndThenTests(ClosingServerResumesAwaiters) addTest(DoesntCoawaitNonlisteningServer) addTest(DoesntCoawaitWithPendingConnection) }; QTEST_GUILESS_MAIN(QCoroWebSocketServerTest) #include "qcorowebsocketserver.moc" qcoro-0.12.0/tests/qdbuspendingcall.cpp000066400000000000000000000072301477357142500201230ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "testdbusserver.h" #include "testobject.h" #include "qcorodbuspendingcall.h" #include #include #include #include class QCoroDBusPendingCallTest : public QCoro::TestObject { Q_OBJECT private: QCoro::Task<> testTriggers_coro(QCoro::TestContext) { QDBusInterface iface(DBusServer::serviceName, DBusServer::objectPath, DBusServer::interfaceName); QCORO_VERIFY(iface.isValid()); const QDBusReply reply = co_await iface.asyncCall(QStringLiteral("foo")); QCORO_VERIFY(reply.isValid()); } QCoro::Task<> testReturnsResult_coro(QCoro::TestContext) { QDBusInterface iface(DBusServer::serviceName, DBusServer::objectPath, DBusServer::interfaceName); QCORO_VERIFY(iface.isValid()); const QDBusReply reply = co_await iface.asyncCall(QStringLiteral("ping"), QStringLiteral("Hello there!")); QCORO_VERIFY(reply.isValid()); QCORO_COMPARE(reply.value(), QStringLiteral("Hello there!")); } void testThenReturnsResult_coro(TestLoop &el) { QDBusInterface iface(DBusServer::serviceName, DBusServer::objectPath, DBusServer::interfaceName); QVERIFY(iface.isValid()); const QDBusPendingCall call = iface.asyncCall(QStringLiteral("ping"), QStringLiteral("Hello there!")); bool called = false; qCoro(call).waitForFinished().then([&](const QDBusMessage &msg) { called = true; el.quit(); QCOMPARE(QDBusReply(msg).value(), QStringLiteral("Hello there!")); }); el.exec(); QVERIFY(called); } QCoro::Task<> testDoesntBlockEventLoop_coro(QCoro::TestContext) { QCoro::EventLoopChecker eventLoopResponsive; QDBusInterface iface(DBusServer::serviceName, DBusServer::objectPath, DBusServer::interfaceName); QCORO_VERIFY(iface.isValid()); const QDBusReply reply = co_await iface.asyncCall(QStringLiteral("blockFor"), 1); QCORO_VERIFY(reply.isValid()); QCORO_VERIFY(eventLoopResponsive); } QCoro::Task<> testDoesntCoAwaitFinishedCall_coro(QCoro::TestContext test) { QDBusInterface iface(DBusServer::serviceName, DBusServer::objectPath, DBusServer::interfaceName); QCORO_VERIFY(iface.isValid()); auto call = iface.asyncCall(QStringLiteral("foo")); QDBusReply reply = co_await call; QCORO_VERIFY(reply.isValid()); test.setShouldNotSuspend(); reply = co_await call; QCORO_VERIFY(reply.isValid()); } private Q_SLOTS: void initTestCase() { for (int i = 0; i < 10; ++i) { QDBusInterface iface(DBusServer::serviceName, DBusServer::objectPath, DBusServer::interfaceName); if (iface.isValid()) { return; } QTest::qWait(100); } QFAIL("Failed to obtain a valid dbus interface"); } void cleanupTestCase() { QDBusInterface iface(DBusServer::serviceName, DBusServer::objectPath, DBusServer::interfaceName); iface.call(QStringLiteral("quit")); } addTest(Triggers) addCoroAndThenTests(ReturnsResult) addTest(DoesntBlockEventLoop) addTest(DoesntCoAwaitFinishedCall) }; DBUS_TEST_MAIN(QCoroDBusPendingCallTest) #include "qdbuspendingcall.moc" qcoro-0.12.0/tests/qdbuspendingreply.cpp000066400000000000000000000156201477357142500203450ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcorodbustestinterface.h" #include "testdbusserver.h" #include "testobject.h" #include "qcorodbuspendingreply.h" #include #include #include #include #include #include class QCoroDBusPendingCallTest : public QCoro::TestObject { Q_OBJECT private: QCoro::Task<> testTriggers_coro(QCoro::TestContext) { cz::dvratil::qcorodbustest iface(DBusServer::serviceName, DBusServer::objectPath, QDBusConnection::sessionBus()); QCORO_VERIFY(iface.isValid()); const auto resp = co_await iface.foo(); QCORO_VERIFY(resp.isFinished()); } QCoro::Task<> testQCoroWrapperTriggers_coro(QCoro::TestContext) { cz::dvratil::qcorodbustest iface(DBusServer::serviceName, DBusServer::objectPath, QDBusConnection::sessionBus()); QCORO_VERIFY(iface.isValid()); const auto resp = co_await qCoro(iface.foo()).waitForFinished(); QCORO_VERIFY(resp.isFinished()); } void testThenQCoroWrapperTriggers_coro(TestLoop &el) { cz::dvratil::qcorodbustest iface(DBusServer::serviceName, DBusServer::objectPath, QDBusConnection::sessionBus()); QVERIFY(iface.isValid()); bool called = false; qCoro(iface.foo()).waitForFinished().then([&](QDBusPendingReply<> reply) { called = true; el.quit(); QVERIFY(reply.isFinished()); }); el.exec(); QVERIFY(called); } QCoro::Task<> testReturnsResult_coro(QCoro::TestContext) { cz::dvratil::qcorodbustest iface(DBusServer::serviceName, DBusServer::objectPath, QDBusConnection::sessionBus()); QCORO_VERIFY(iface.isValid()); const QString reply = co_await iface.ping(QStringLiteral("Hello there!")); QCORO_COMPARE(reply, QStringLiteral("Hello there!")); } void testThenReturnsResult_coro(TestLoop &el) { cz::dvratil::qcorodbustest iface(DBusServer::serviceName, DBusServer::objectPath, QDBusConnection::sessionBus()); QVERIFY(iface.isValid()); bool called = false; qCoro(iface.ping(QStringLiteral("Hello there!"))).waitForFinished().then( [&](const QDBusPendingReply &reply) { called = true; el.quit(); QCOMPARE(reply.value(), QStringLiteral("Hello there!")); }); el.exec(); QVERIFY(called); } QCoro::Task<> testReturnsBlockingResult_coro(QCoro::TestContext) { cz::dvratil::qcorodbustest iface(DBusServer::serviceName, DBusServer::objectPath, QDBusConnection::sessionBus()); QCORO_VERIFY(iface.isValid()); const QString reply = co_await iface.blockAndReturn(1); QCORO_COMPARE(reply, QStringLiteral("Slept for 1 seconds")); } QCoro::Task<> testDoesntBlockEventLoop_coro(QCoro::TestContext) { QCoro::EventLoopChecker eventLoopResponsive; cz::dvratil::qcorodbustest iface(DBusServer::serviceName, DBusServer::objectPath, QDBusConnection::sessionBus()); QCORO_VERIFY(iface.isValid()); const auto result = co_await iface.blockFor(1); QCORO_VERIFY(result.isFinished()); QCORO_VERIFY(eventLoopResponsive); } QCoro::Task<> testDoesntCoAwaitFinishedCall_coro(QCoro::TestContext test) { cz::dvratil::qcorodbustest iface(DBusServer::serviceName, DBusServer::objectPath, QDBusConnection::sessionBus()); QCORO_VERIFY(iface.isValid()); auto call = iface.foo(); QDBusReply reply = co_await call; QCORO_VERIFY(reply.isValid()); test.setShouldNotSuspend(); reply = co_await call; QCORO_VERIFY(reply.isValid()); } void testThenDoesntCoAwaitFinishedCall_coro(TestLoop &el) { cz::dvratil::qcorodbustest iface(DBusServer::serviceName, DBusServer::objectPath, QDBusConnection::sessionBus()); QVERIFY(iface.isValid()); auto call = iface.foo(); call.waitForFinished(); bool called = false; qCoro(call).waitForFinished().then([&](QDBusPendingReply<>) { called = true; el.quit(); }); el.exec(); QVERIFY(called); } QCoro::Task<> testHandlesMultipleArguments_coro(QCoro::TestContext) { cz::dvratil::qcorodbustest iface(DBusServer::serviceName, DBusServer::objectPath, QDBusConnection::sessionBus()); QCORO_VERIFY(iface.isValid()); QDBusPendingReply reply = iface.asyncCall(QStringLiteral("blockAndReturnMultipleArguments"), 1); co_await reply; QCORO_VERIFY(reply.isFinished()); QCORO_COMPARE(reply.argumentAt<0>(), QStringLiteral("Hello World!")); QCORO_COMPARE(reply.argumentAt<1>(), true); } void testThenHandlesMultipleArguments_coro(TestLoop &el) { cz::dvratil::qcorodbustest iface(DBusServer::serviceName, DBusServer::objectPath, QDBusConnection::sessionBus()); QVERIFY(iface.isValid()); QDBusPendingReply reply = iface.asyncCall(QStringLiteral("blockAndReturnMultipleArguments"), 1); bool called = false; qCoro(reply).waitForFinished().then([&](const QDBusPendingReply &reply) { called = true; el.quit(); QCOMPARE(reply.argumentAt<0>(), QStringLiteral("Hello World!")); QCOMPARE(reply.argumentAt<1>(), true); }); el.exec(); QVERIFY(called); } private Q_SLOTS: void initTestCase() { for (int i = 0; i < 10; ++i) { QDBusInterface iface(DBusServer::serviceName, DBusServer::objectPath, DBusServer::interfaceName); if (iface.isValid()) { return; } QTest::qWait(100); } QFAIL("Failed to obtain a valid dbus interface"); } void cleanupTestCase() { QDBusInterface iface(DBusServer::serviceName, DBusServer::objectPath, DBusServer::interfaceName); iface.call(QStringLiteral("quit")); } addTest(Triggers) addCoroAndThenTests(QCoroWrapperTriggers) addCoroAndThenTests(ReturnsResult) addTest(ReturnsBlockingResult) addTest(DoesntBlockEventLoop) addCoroAndThenTests(DoesntCoAwaitFinishedCall) addCoroAndThenTests(HandlesMultipleArguments) }; DBUS_TEST_MAIN(QCoroDBusPendingCallTest) #include "qdbuspendingreply.moc" qcoro-0.12.0/tests/qfuture.cpp000066400000000000000000000215611477357142500163020ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "testobject.h" #include "qcorofuture.h" #include #include #include #if QT_VERSION_MAJOR > 6 #include #endif #include class TestException : public QException { public: explicit TestException(const QString &msg) : mMsg(msg) {} const char *what() const noexcept override { return qUtf8Printable(mMsg); } TestException *clone() const override { return new TestException(mMsg); } void raise() const override { throw *this; } private: QString mMsg; }; class MoveOnly { public: explicit MoveOnly(int value) : value(value) {} MoveOnly(const MoveOnly &) = delete; MoveOnly(MoveOnly &&) = default; MoveOnly &operator=(const MoveOnly &) = delete; MoveOnly &operator=(MoveOnly &&) = default; ~MoveOnly() = default; int value = 0; }; class QCoroFutureTest : public QCoro::TestObject { Q_OBJECT private: QCoro::Task<> testTriggers_coro(QCoro::TestContext) { auto future = QtConcurrent::run([] { std::this_thread::sleep_for(100ms); }); co_await future; QCORO_VERIFY(future.isFinished()); } QCoro::Task<> testQCoroWrapperTriggers_coro(QCoro::TestContext) { auto future = QtConcurrent::run([] { std::this_thread::sleep_for(100ms); }); co_await qCoro(future).waitForFinished(); QCORO_VERIFY(future.isFinished()); } void testThenQCoroWrapperTriggers_coro(TestLoop &el) { auto future = QtConcurrent::run([] { std::this_thread::sleep_for(100ms); }); bool called = false; qCoro(future).waitForFinished().then([&]() { called = true; el.quit(); }); el.exec(); QVERIFY(called); } QCoro::Task<> testReturnsResult_coro(QCoro::TestContext) { const QString result = co_await QtConcurrent::run([] { std::this_thread::sleep_for(100ms); return QStringLiteral("42"); }); QCORO_COMPARE(result, QStringLiteral("42")); } void testThenReturnsResult_coro(TestLoop &el) { const auto future = QtConcurrent::run([] { std::this_thread::sleep_for(100ms); return QStringLiteral("42"); }); bool called = false; qCoro(future).waitForFinished().then([&](const QString &result) { called = true; el.quit(); QCOMPARE(result, QStringLiteral("42")); }); el.exec(); QVERIFY(called); } QCoro::Task<> testDoesntBlockEventLoop_coro(QCoro::TestContext) { QCoro::EventLoopChecker eventLoopResponsive; co_await QtConcurrent::run([] { std::this_thread::sleep_for(500ms); }); QCORO_VERIFY(eventLoopResponsive); } QCoro::Task<> testDoesntCoAwaitFinishedFuture_coro(QCoro::TestContext test) { auto future = QtConcurrent::run([] { std::this_thread::sleep_for(100ms); }); co_await future; QCORO_VERIFY(future.isFinished()); test.setShouldNotSuspend(); co_await future; } void testThenDoesntCoAwaitFinishedFuture_coro(TestLoop &el) { auto future = QtConcurrent::run([] { std::this_thread::sleep_for(1ms); }); QTest::qWait((100ms).count()); QVERIFY(future.isFinished()); bool called = false; qCoro(future).waitForFinished().then([&]() { called = true; el.quit(); }); el.exec(); QVERIFY(called); } QCoro::Task<> testDoesntCoAwaitCanceledFuture_coro(QCoro::TestContext test) { test.setShouldNotSuspend(); QFuture future; co_await future; } void testThenDoesntCoAwaitCanceledFuture_coro(TestLoop &el) { QFuture future; bool called = false; qCoro(future).waitForFinished().then([&]() { called = true; el.quit(); }); el.exec(); QVERIFY(called); } QCoro::Task<> testPropagateQExceptionFromVoidConcurrent_coro(QCoro::TestContext) { auto future = QtConcurrent::run([]() { std::this_thread::sleep_for(100ms); throw TestException(QStringLiteral("Ooops")); }); QCORO_VERIFY_EXCEPTION_THROWN(co_await future, TestException); } QCoro::Task<> testPropagateQExceptionFromNonvoidConcurrent_coro(QCoro::TestContext) { bool throwException = true; auto future = QtConcurrent::run([throwException]() -> int { std::this_thread::sleep_for(100ms); if (throwException) { // Workaround MSVC reporting the "return" stmt as unreachablet throw TestException(QStringLiteral("Ooops")); } return 42; }); QCORO_VERIFY_EXCEPTION_THROWN(co_await future, TestException); } #if QT_VERSION_MAJOR >= 6 QCoro::Task<> testPropagateQExceptionFromVoidPromise_coro(QCoro::TestContext) { QPromise promise; QTimer::singleShot(100ms, this, [&promise]() { promise.start(); promise.setException(TestException(QStringLiteral("Booom"))); promise.finish(); }); QCORO_VERIFY_EXCEPTION_THROWN(co_await promise.future(), TestException); } QCoro::Task<> testPropagateQExceptionFromNonvoidPromise_coro(QCoro::TestContext) { QPromise promise; QTimer::singleShot(100ms, this, [&promise]() { promise.start(); promise.setException(TestException(QStringLiteral("Booom"))); promise.finish(); }); QCORO_VERIFY_EXCEPTION_THROWN(co_await promise.future(), TestException); } QCoro::Task<> testPropagateStdExceptionFromVoidPromise_coro(QCoro::TestContext) { QPromise promise; QTimer::singleShot(100ms, this, [&promise]() { promise.start(); promise.setException(std::make_exception_ptr(std::runtime_error("Booom"))); promise.finish(); }); QCORO_VERIFY_EXCEPTION_THROWN(co_await promise.future(), std::runtime_error); } QCoro::Task<> testPropagateStdExceptionFromNonvoidPromise_coro(QCoro::TestContext) { QPromise promise; QTimer::singleShot(100ms, this, [&promise]() { promise.start(); promise.setException(std::make_exception_ptr(std::runtime_error("Booom"))); promise.finish(); }); QCORO_VERIFY_EXCEPTION_THROWN(co_await promise.future(), std::runtime_error); } QCoro::Task<> testTakeResult_coro(QCoro::TestContext) { auto future = QtConcurrent::run([]() -> MoveOnly { std::this_thread::sleep_for(10ms); return MoveOnly(42); }); MoveOnly result = co_await qCoro(future).takeResult(); QCORO_COMPARE(result.value, 42); QPromise promise; QTimer::singleShot(10ms, this, [&promise]() { promise.start(); promise.addResult(MoveOnly(84)); promise.finish(); }); QCORO_COMPARE((co_await qCoro(promise.future()).takeResult()).value, 84); } void testThenTakeResult_coro(TestLoop &el) { auto future = QtConcurrent::run([]() -> MoveOnly { std::this_thread::sleep_for(10ms); return MoveOnly(42); }); bool called = false; qCoro(future).takeResult().then([&](MoveOnly result) { called = true; QCOMPARE(result.value, 42); el.quit(); }); el.exec(); QVERIFY(called); } #endif // QPromise cancelling running future on destruction has been introduced in // Qt 6.3. #if QT_VERSION >= QT_VERSION_CHECK(6, 3, 1) QCoro::Task<> testUnfinishedPromiseDestroyed_coro(QCoro::TestContext) { const auto future = [this]() { auto promise = std::make_shared>(); auto future = promise->future(); QTimer::singleShot(400ms, this, [p = promise]() { p->addResult(42); }); return future; }(); co_await future; } #endif private Q_SLOTS: addTest(Triggers) addCoroAndThenTests(ReturnsResult) addTest(DoesntBlockEventLoop) addCoroAndThenTests(DoesntCoAwaitFinishedFuture) addCoroAndThenTests(DoesntCoAwaitCanceledFuture) addCoroAndThenTests(QCoroWrapperTriggers) addTest(PropagateQExceptionFromVoidConcurrent) addTest(PropagateQExceptionFromNonvoidConcurrent) #if QT_VERSION_MAJOR >= 6 addTest(PropagateQExceptionFromVoidPromise) addTest(PropagateQExceptionFromNonvoidPromise) addTest(PropagateStdExceptionFromVoidPromise) addTest(PropagateStdExceptionFromNonvoidPromise) addCoroAndThenTests(TakeResult) #endif #if QT_VERSION >= QT_VERSION_CHECK(6, 3, 1) addTest(UnfinishedPromiseDestroyed) #endif }; QTEST_GUILESS_MAIN(QCoroFutureTest) #include "qfuture.moc" qcoro-0.12.0/tests/qtimer.cpp000066400000000000000000000046551477357142500161150ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "testobject.h" #include "qcorotimer.h" #include #include using namespace std::chrono_literals; class QCoroTimerTest : public QCoro::TestObject { Q_OBJECT private: QCoro::Task<> testTriggers_coro(QCoro::TestContext) { QTimer timer; timer.setInterval(100ms); timer.start(); co_await timer; } QCoro::Task<> testQCoroWrapperTriggers_coro(QCoro::TestContext) { QTimer timer; timer.setInterval(100ms); timer.start(); co_await qCoro(timer).waitForTimeout(); } QCoro::Task<> testDoesntBlockEventLoop_coro(QCoro::TestContext) { QCoro::EventLoopChecker eventLoopResponsive; QTimer timer; timer.setInterval(500ms); timer.start(); co_await timer; QCORO_VERIFY(eventLoopResponsive); } QCoro::Task<> testDoesntCoAwaitInactiveTimer_coro(QCoro::TestContext ctx) { ctx.setShouldNotSuspend(); QTimer timer; timer.setInterval(1s); // Don't start the timer! co_await timer; } QCoro::Task<> testDoesntCoAwaitNullTimer_coro(QCoro::TestContext ctx) { ctx.setShouldNotSuspend(); QTimer *timer = nullptr; co_await timer; } void testThenTriggers_coro(TestLoop &el) { QTimer timer; bool triggered = false; timer.start(10ms); qCoro(timer).waitForTimeout().then([&el, &triggered]() { triggered = true; el.quit(); }); el.exec(); QVERIFY(triggered); } QCoro::Task<> testSleepFor_coro(QCoro::TestContext) { QElapsedTimer elapsed; elapsed.start(); co_await QCoro::sleepFor(100ms); QCORO_VERIFY(elapsed.elapsed() >= 75); } QCoro::Task<> testSleepUntil_coro(QCoro::TestContext) { QElapsedTimer elapsed; elapsed.start(); co_await QCoro::sleepUntil(std::chrono::steady_clock::now() + 500ms); QCORO_VERIFY(elapsed.elapsed() >= 475); } private Q_SLOTS: addTest(Triggers) addTest(QCoroWrapperTriggers) addTest(DoesntBlockEventLoop) addTest(DoesntCoAwaitInactiveTimer) addTest(DoesntCoAwaitNullTimer) addTest(SleepFor) addTest(SleepUntil) addThenTest(Triggers) }; QTEST_GUILESS_MAIN(QCoroTimerTest) #include "qtimer.moc" qcoro-0.12.0/tests/testconstraints.cpp000066400000000000000000000051621477357142500200550ustar00rootroot00000000000000#include #include #include "qcoro/coroutine.h" #include "qcoro/qcorotask.h" #include "qcoro/qcorolazytask.h" namespace helper { template struct is_awaitable { static constexpr bool value = true; }; template static constexpr bool is_awaitable_v = is_awaitable::value; } // namespace helper struct TestAwaitable { bool await_ready() const { return true; } void await_suspend(std::coroutine_handle<>); void await_resume() const; }; struct TestAwaitableWithOperatorCoAwait { TestAwaitable operator co_await() { return TestAwaitable{}; } }; namespace TestNS { struct TestAwaitableWithNonmemberOperatorCoAwait {}; #if !defined(_MSC_VER) || defined(__clang__) // This is how it's supposed to work: the operator must be defined in the same namespace // as the argument type and is discovered via ADL. auto operator co_await(TestAwaitableWithNonmemberOperatorCoAwait &&) { return TestAwaitable{}; } #endif } // namespace TestNS #if defined(_MSC_VER) && !defined(__clang__) // Unfortunately, MSVC is only able to find the operator in global namespace :( // This is most likely a bug in MSVC. auto operator co_await(TestNS::TestAwaitableWithNonmemberOperatorCoAwait &&) { return TestAwaitable{}; } #endif class TestConstraints : public QObject { Q_OBJECT private Q_SLOTS: void testAwaitableConcept() { static_assert(helper::is_awaitable_v>, "Awaitable concept doesn't accept Task, although it should."); static_assert(helper::is_awaitable_v>, "Awaitable concept doesn't accept Task, although it should."); static_assert(helper::is_awaitable_v>, "Awaitable concept doesn't accept LazyTask, although it should."); static_assert(helper::is_awaitable_v>, "Awaitable concept doesn't accept LazyTask, although it should."); static_assert(helper::is_awaitable_v, "Awaitable concept doesn't accept an awaitable with await member functions."); static_assert(helper::is_awaitable_v, "Awaitable concept doesn't accept an awaitable with member operator co_await."); static_assert(helper::is_awaitable_v, "Awaitable concept doesn't accept an awaitable with non-member operator co_await."); } }; QTEST_GUILESS_MAIN(TestConstraints) #include "testconstraints.moc" qcoro-0.12.0/tests/testhttpserver.cpp000066400000000000000000000037601477357142500177160ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "testlibs/testhttpserver.h" #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(std::chrono::seconds) using namespace std::chrono_literals; class TestHttpServerTest : public QObject { Q_OBJECT private Q_SLOTS: void init() { mServer.start(QHostAddress::LocalHost); } void cleanup() { mServer.stop(); } void testGet_data() { QTest::addColumn("url"); QTest::addColumn("expectedData"); QTest::addColumn("timeout"); QTest::newRow("/") << QStringLiteral("http://localhost:%1/") << QByteArray{"abcdef"} << 5s; QTest::newRow("/block") << QStringLiteral("http://localhost:%1/block") << QByteArray{"abcdef"} << 5s; QTest::newRow("/stream") << QStringLiteral("http://localhost:%1/stream") << QByteArray{"Hola 0\nHola 1\nHola 2\nHola 3\nHola 4\nHola " "5\nHola 6\nHola 7\nHola 8\nHola 9\n"} << 15s; } void testGet() { QFETCH(QString, url); QFETCH(QByteArray, expectedData); QFETCH(std::chrono::seconds, timeout); QNetworkAccessManager nam; QEventLoop el; auto reply = std::unique_ptr( nam.get(QNetworkRequest{QUrl{url.arg(mServer.port())}})); connect(reply.get(), &QNetworkReply::finished, &el, &QEventLoop::quit); QTimer::singleShot(timeout, &el, [&el]() mutable { el.exit(1); }); QCOMPARE(el.exec(), 0); QCOMPARE(reply->readAll(), expectedData); } private: TestHttpServer mServer; }; QTEST_GUILESS_MAIN(TestHttpServerTest) #include "testhttpserver.moc" qcoro-0.12.0/tests/testlibs/000077500000000000000000000000001477357142500157275ustar00rootroot00000000000000qcoro-0.12.0/tests/testlibs/CMakeLists.txt000066400000000000000000000030341477357142500204670ustar00rootroot00000000000000add_library(qcoro_testlib STATIC testobject.cpp testloop.cpp ) target_include_directories(qcoro_testlib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(qcoro_testlib PUBLIC QCoro${QT_VERSION_MAJOR}Core QCoro${QT_VERSION_MAJOR}Test Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Test ) if (QCORO_WITH_QTDBUS) add_executable(testdbusserver EXCLUDE_FROM_ALL testdbusserver.cpp) target_link_libraries(testdbusserver Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::DBus ) set(qcoro_test_dbus_SRCS) if (${QT_VERSION_MAJOR} EQUAL 5) qt5_add_dbus_interface(qcoro_test_dbus_SRCS cz.dvratil.qcorodbustest.xml qcorodbustestinterface) else() qt_add_dbus_interface(qcoro_test_dbus_SRCS cz.dvratil.qcorodbustest.xml qcorodbustestinterface) endif() add_library(qcoro_test_dbus OBJECT ${qcoro_test_dbus_SRCS}) target_include_directories(qcoro_test_dbus PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ) target_link_libraries(qcoro_test_dbus PUBLIC Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::DBus ) endif() if (QCORO_WITH_QTWEBSOCKETS) set(qcoro_test_ws_SRCS testwsserver.cpp) add_library(qcoro_test_ws OBJECT ${qcoro_test_ws_SRCS}) target_include_directories(qcoro_test_ws PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(qcoro_test_ws PUBLIC Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Test Qt${QT_VERSION_MAJOR}::WebSockets ) endif() qcoro-0.12.0/tests/testlibs/cz.dvratil.qcorodbustest.xml000066400000000000000000000020241477357142500234270ustar00rootroot00000000000000 qcoro-0.12.0/tests/testlibs/testdbusserver.cpp000066400000000000000000000041351477357142500215220ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "testdbusserver.h" #include #include #include #include #include #include #include using namespace std::chrono_literals; DBusServer::DBusServer() { QTimer::singleShot(0, this, &DBusServer::run); // Self-terminate if there's no interaction from a client in 30 seconds. // Prevents leaking the test server in case the test crashes. mSuicideTimer.setInterval(30s); mSuicideTimer.setSingleShot(true); connect(&mSuicideTimer, &QTimer::timeout, this, []() { std::cerr << "No call in 30 seconds, terminating!" << std::endl; qApp->exit(1); }); } void DBusServer::run() { auto conn = QDBusConnection::sessionBus(); if (!conn.registerService(serviceName)) { qWarning() << "Failed to register service to DBus:" << conn.lastError().message(); } if (!conn.registerObject(objectPath, interfaceName, this, QDBusConnection::ExportAllSlots)) { qWarning() << "Failed to register object to DBus" << conn.lastError().message(); } mSuicideTimer.start(); } void DBusServer::foo() { mSuicideTimer.start(); } void DBusServer::blockFor(int seconds) { std::this_thread::sleep_for(std::chrono::seconds(seconds)); mSuicideTimer.start(); } QString DBusServer::blockAndReturn(int seconds) { std::this_thread::sleep_for(std::chrono::seconds(seconds)); mSuicideTimer.start(); return QStringLiteral("Slept for %1 seconds").arg(seconds); } QString DBusServer::blockAndReturnMultipleArguments(int seconds, bool &out) { std::this_thread::sleep_for(std::chrono::seconds{seconds}); mSuicideTimer.start(); out = true; return QStringLiteral("Hello World!"); } QString DBusServer::ping(const QString &ping) { mSuicideTimer.start(); return ping; } void DBusServer::quit() { mSuicideTimer.stop(); qApp->quit(); } int main(int argc, char **argv) { QCoreApplication app(argc, argv); DBusServer server; return app.exec(); } qcoro-0.12.0/tests/testlibs/testdbusserver.h000066400000000000000000000101231477357142500211610ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include #include #include #include #include #include class DBusServer : public QObject { Q_OBJECT public: inline static const QString serviceName = QStringLiteral("cz.dvratil.qcorodbustest"); inline static const QString interfaceName = QStringLiteral("cz.dvratil.qcorodbustest"); inline static const QString objectPath = QStringLiteral("/"); explicit DBusServer(); void run(); public Q_SLOTS: void foo(); QString ping(const QString &ping); void blockFor(int seconds); QString blockAndReturn(int seconds); QString blockAndReturnMultipleArguments(int seconds, bool &out); void quit(); private: QTimer mSuicideTimer; }; // We must run the DBus server into its own process due to QTBUG-92107 (asyncCall blocks if the // remote service is registered in the same process (even if it lives in a different thread) #define DBUS_TEST_MAIN(TestClass) \ bool startDBusServer(QProcess &process) { \ process.start(QStringLiteral(TESTDBUSSERVER_EXECUTABLE), QStringList{}); \ if (!process.waitForStarted()) { \ std::cerr << "Failed to start testdbusserver" << std::endl; \ return false; \ } \ return true; \ } \ \ bool stopDBusServer(QProcess &process) { \ process.waitForFinished(); \ if (process.exitCode() != 0) { \ std::cerr << "testdbuserver terminated with exit code " << process.exitCode() \ << std::endl; \ return false; \ } \ return true; \ } \ \ int main(int argc, char **argv) { \ QCoreApplication app(argc, argv); \ QProcess dbusServer; \ if (!startDBusServer(dbusServer)) \ return 1; \ TestClass testClass; \ QTEST_SET_MAIN_SOURCE_PATH \ const int result = QTest::qExec(&testClass, argc, argv); \ if (!stopDBusServer(dbusServer)) \ return 1; \ return result; \ } qcoro-0.12.0/tests/testlibs/testhttpserver.h000066400000000000000000000121531477357142500212100ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include #include #include #include #include #include class QTcpServer; class QTcpSocket; class QLocalServer; class QLocalSocket; template class Thread : public QThread { public: explicit Thread(Func &&f) : mFunc(std::forward(f)) {} ~Thread() = default; void run() override { mFunc(); } private: Func mFunc; }; template Thread(Func &&) -> Thread; template struct socket_for_server {}; template<> struct socket_for_server { using type = QTcpSocket; }; template<> struct socket_for_server { using type = QLocalSocket; }; template class TestHttpServer { using SocketType = typename socket_for_server::type; public: template void start(const T &name) { mPort = 0; mHasConnection = false; mStop = false; mExpectTimeout = false; // Can't use QThread::create, it's only available when Qt is built with C++17, // which some distros don't have :( mThread.reset(new Thread([this, name]() { run(name); })); mThread->start(); std::unique_lock lock(mReadyMutex); mServerReady.wait(lock, [this]() { return mPort != 0; }); } void stop() { mStop = true; if (mThread->isRunning()) { mThread->wait(); } mThread.reset(); mPort = 0; mHasConnection = false; } uint16_t port() const { return mPort; } void setExpectTimeout(bool expectTimeout) { mExpectTimeout = expectTimeout; } bool waitForConnection() { std::unique_lock lock(mReadyMutex); return mServerReady.wait_for(lock, std::chrono::seconds(5), [this]() { return mHasConnection; }); } private: template void run(const T &name) { using namespace std::chrono_literals; ServerType server{}; if (!server.listen(name)) { qDebug() << "Error listening:" << server.serverError(); return; } assert(server.isListening()); { std::scoped_lock lock(mReadyMutex); if constexpr (std::is_same_v) { mPort = server.serverPort(); } else { mPort = 1; } } SocketType *conn = nullptr; mServerReady.notify_all(); for (int i = 0; i < 10 && !mStop; ++i) { if (server.waitForNewConnection(1000)) { conn = server.nextPendingConnection(); break; } } if (!conn) { if (!mExpectTimeout) { QFAIL("No incoming connection within timeout!"); } mPort = 0; return; } mHasConnection = true; mServerReady.notify_all(); if (conn->waitForReadyRead(10000)) { const auto request = conn->readLine(); qDebug() << request; if (request == "GET /stream HTTP/1.1\r\n") { QStringList lines; for (int i = 0; i < 10; ++i) { lines.push_back(QStringLiteral("Hola %1\n").arg(i)); } const auto len = std::accumulate(lines.cbegin(), lines.cend(), 0, [](int l, const QString &s) { return l + s.size(); }); conn->write("HTTP/1.1 200 OK\r\n" "Content-Type: text/plain\r\n" "Content-Length: " + QByteArray::number(len) + "\r\n" "\r\n"); conn->flush(); for (const auto &line : lines) { conn->write(line.toUtf8()); conn->flush(); std::this_thread::sleep_for(100ms); } } else { if (request == "GET /block HTTP/1.1\r\n") { std::this_thread::sleep_for(500ms); } conn->write("HTTP/1.1 200 OK\r\n" "Content-Type: text/plain\r\n" "\r\n" "abcdef"); } conn->flush(); conn->close(); } else if (!mStop) { if (conn->state() == std::remove_cvref_t::ConnectedState) { if (!mExpectTimeout) { QFAIL("No request within 10 seconds"); } } else { qDebug() << "Client disconnected without sending request"; } } delete conn; mPort = 0; } std::unique_ptr mThread; std::mutex mReadyMutex; std::condition_variable mServerReady; uint16_t mPort = 0; bool mHasConnection = false; std::atomic_bool mStop = false; std::atomic_bool mExpectTimeout = false; }; qcoro-0.12.0/tests/testlibs/testloop.cpp000066400000000000000000000006351477357142500203100ustar00rootroot00000000000000#include "testloop.h" #include TestLoop::TestLoop(QObject *parent) : QObject(parent) { mTimer.setSingleShot(true); connect(&mTimer, &QTimer::timeout, this, [this]() { mEventLoop.quit(); QFAIL("Test timeout!"); }); } void TestLoop::exec() { mEventLoop.exec(); } void TestLoop::quit() { mTimer.stop(); QTimer::singleShot(0, &mEventLoop, &QEventLoop::quit); } qcoro-0.12.0/tests/testlibs/testloop.h000066400000000000000000000005051477357142500177510ustar00rootroot00000000000000#pragma once #include #include #define DELAYED(expr) \ QTimer::singleShot(10ms, [&]() { expr; }) class TestLoop : public QObject { Q_OBJECT public: explicit TestLoop(QObject *parent = nullptr); void exec(); void quit(); private: QEventLoop mEventLoop; QTimer mTimer; }; qcoro-0.12.0/tests/testlibs/testmacros.h000066400000000000000000000010601477357142500202610ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once //! Executes given \c expr with 10ms delay. #define QCORO_DELAY(expr) \ QTimer::singleShot(10ms, [&]() { expr; }) #define QCORO_TEST_TIMEOUT(expr) { \ const auto start = std::chrono::steady_clock::now(); \ const bool ok = expr; \ const auto end = std::chrono::steady_clock::now(); \ QCORO_VERIFY(!ok); \ QCORO_VERIFY((end - start) < 500ms); \ } qcoro-0.12.0/tests/testlibs/testobject.cpp000066400000000000000000000014441477357142500206040ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "testobject.h" using namespace QCoro; TestContext::TestContext(QEventLoop &el) : mEventLoop(&el) { mEventLoop->setProperty("testFinished", false); mEventLoop->setProperty("shouldNotSuspend", false); } TestContext::TestContext(TestContext &&other) noexcept { std::swap(mEventLoop, other.mEventLoop); } TestContext::~TestContext() { if (mEventLoop) { mEventLoop->setProperty("testFinished", true); mEventLoop->quit(); } } TestContext &TestContext::operator=(TestContext &&other) noexcept { std::swap(mEventLoop, other.mEventLoop); return *this; } void TestContext::setShouldNotSuspend() { mEventLoop->setProperty("shouldNotSuspend", true); } qcoro-0.12.0/tests/testlibs/testobject.h000066400000000000000000000057521477357142500202570ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include #include #include #include #include #include "qcorotask.h" #include "qcorotest.h" #include "testmacros.h" #include "testloop.h" #include using namespace std::chrono_literals; namespace QCoro { class TestContext { public: TestContext(QEventLoop &el); TestContext(TestContext &&) noexcept; TestContext(const TestContext &) = delete; ~TestContext(); TestContext &operator=(TestContext &&) noexcept; TestContext &operator=(const TestContext &) = delete; void setShouldNotSuspend(); private: QEventLoop *mEventLoop = {}; }; class EventLoopChecker : public QTimer { Q_OBJECT public: explicit EventLoopChecker(int minTicks = 10, std::chrono::milliseconds interval = 5ms) : mMinTicks{minTicks} { connect(this, &EventLoopChecker::timeout, this, [this]() { ++mTick; }); setInterval(interval); start(); } operator bool() const { if (mTick < mMinTicks) { qDebug() << "EventLoopChecker failed: ticks=" << mTick << ", minTicks=" << mMinTicks; } return mTick >= mMinTicks; } private: int mTick = 0; int mMinTicks = 10; }; template class TestObject : public QObject { protected: explicit TestObject(QObject *parent = nullptr) : QObject(parent) {} void coroWrapper(QCoro::Task<> (TestClass::*testFunction)(TestContext)) { QEventLoop el; QTimer::singleShot(5s, &el, [&el]() mutable { el.exit(1); }); (static_cast(this)->*testFunction)(el); bool testFinished = el.property("testFinished").toBool(); const bool shouldNotSuspend = el.property("shouldNotSuspend").toBool(); if (testFinished) { QVERIFY(shouldNotSuspend); } else { QVERIFY(!shouldNotSuspend); const auto result = el.exec(); QVERIFY2(result == 0, "Test function has timed out"); testFinished = el.property("testFinished").toBool(); QVERIFY(testFinished); } } }; #define addTest(name) \ void test##name() { \ coroWrapper(&std::remove_cvref_t::test##name##_coro); \ } #define addThenTest(name) \ void testThen##name() { \ TestLoop loop; \ testThen##name##_coro(loop); \ } #define addCoroAndThenTests(name) \ addTest(name) \ addThenTest(name) } // namespace QCoro qcoro-0.12.0/tests/testlibs/testwsserver.cpp000066400000000000000000000134521477357142500212200ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "testwsserver.h" #include #include #include #include #include #include #include #include using namespace std::chrono_literals; class Server : public QObject { Q_OBJECT public: explicit Server() = default; void setExpectTimeout() { mExpectTimeout = true; } QUrl waitForStart() { std::unique_lock lock(mMutex); mCond.wait(lock, [this]() { return !mUrl.isEmpty(); }); return mUrl; } bool waitForConnection() { std::unique_lock lock(mMutex); if (!mSocket) { return mCond.wait_for(lock, 5s, [this]() -> bool { return mSocket.get() != nullptr; }); } return true; } void start() { mServer.reset(new QWebSocketServer(QStringLiteral("QCoroTestWSServer"), QWebSocketServer::NonSecureMode)); if (!mServer->listen(QHostAddress::LocalHost)) { qCritical() << "WebSocket server failed to start listening"; close(); return; } std::scoped_lock lock(mMutex); mUrl = mServer->serverUrl(); mTimeout.reset(new QTimer()); connect(mTimeout.get(), &QTimer::timeout, this, &Server::newConnectionTimeout); mTimeout->setSingleShot(true); mTimeout->start(10s); connect(mServer.get(), &QWebSocketServer::newConnection, this, &Server::newConnection); connect(mServer.get(), &QWebSocketServer::acceptError, this, [this](auto error) { qCritical() << "WebSocket server failed to accept incoming connection:" << error; close(); }); connect(mServer.get(), &QWebSocketServer::serverError, this, [this](auto error) { qCritical() << "WebSocket server failed to set up WS connection" << error; close(); }); mCond.notify_all(); } void close() { QThread::currentThread()->quit(); mSocket.reset(); mTimeout.reset(); mServer.reset(); } private Q_SLOTS: void newConnectionTimeout() { if (!mExpectTimeout) { QFAIL("No incoming connection within timeout"); } close(); } void newConnection() { mTimeout->stop(); { std::scoped_lock lock(mMutex); mSocket.reset(mServer->nextPendingConnection()); } mCond.notify_all(); mTimeout.reset(new QTimer()); connect(mTimeout.get(), &QTimer::timeout, this, [this]() { if (!mExpectTimeout) { QFAIL("No incoming request within timeout"); } close(); }); mTimeout->setSingleShot(true); mTimeout->start(5s); connect(mSocket.get(), &QWebSocket::textMessageReceived, this, [this](const QString &msg) { mTimeout->stop(); const auto request = mSocket->requestUrl().path(); if (request == QLatin1String("/delay")) { std::this_thread::sleep_for(300ms); mSocket->sendTextMessage(msg); } else if (request == QLatin1String("/large")) { const auto response = QString::fromLatin1(generateLargeMessage().toHex()); mSocket->sendTextMessage(response); } else { mSocket->sendTextMessage(msg); } }); connect(mSocket.get(), &QWebSocket::binaryMessageReceived, this, [this](const QByteArray &msg) { mTimeout->stop(); const auto request = mSocket->requestUrl().path(); if (request == QLatin1String("/delay")) { std::this_thread::sleep_for(100ms); mSocket->sendBinaryMessage(msg); } else if (request == QLatin1String("/large")) { mSocket->sendBinaryMessage(generateLargeMessage()); } else { mSocket->sendBinaryMessage(msg); } }); } private: QByteArray generateLargeMessage() const { constexpr qsizetype size = 10 * 1024 * 1024; /* 10MiB */ std::vector buffer; buffer.resize(size); QRandomGenerator::global()->fillRange(buffer.data(), buffer.size()); QByteArray msg; msg.resize(size); std::memcpy(msg.data(), buffer.data(), buffer.size()); return msg; } std::unique_ptr mSocket; std::unique_ptr mServer; std::unique_ptr mTimeout; std::condition_variable mCond; std::mutex mMutex; std::atomic_bool mExpectTimeout; QUrl mUrl; }; TestWsServer::TestWsServer() = default; TestWsServer::~TestWsServer() = default; void TestWsServer::start() { mThread.reset(new QThread()); mThread->start(); mServer.reset(new Server()); mServer->moveToThread(mThread.get()); QMetaObject::invokeMethod(mServer.get(), &Server::start, Qt::QueuedConnection); mUrl = mServer->waitForStart(); } void TestWsServer::stop() { if (mThread && mThread->isRunning()) { QMetaObject::invokeMethod(mServer.get(), &Server::close, Qt::BlockingQueuedConnection); mThread->wait(); } mThread.reset(); mServer.reset(); mUrl.clear(); } QUrl TestWsServer::url() const { return mUrl; } void TestWsServer::setExpectTimeout() { mServer->setExpectTimeout(); } bool TestWsServer::waitForConnection() { return mServer->waitForConnection(); } #include "testwsserver.moc" qcoro-0.12.0/tests/testlibs/testwsserver.h000066400000000000000000000010331477357142500206550ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include #include #include #include #include #include class Server; class TestWsServer { public: TestWsServer(); ~TestWsServer(); void start(); void stop(); QUrl url() const; bool waitForConnection(); void setExpectTimeout(); private: std::unique_ptr mThread; std::unique_ptr mServer; QUrl mUrl; };