diff --git a/.ci/oauth2/setup.sh b/.ci/oauth2/setup.sh index 49af95fb16..8c11cee739 100755 --- a/.ci/oauth2/setup.sh +++ b/.ci/oauth2/setup.sh @@ -46,9 +46,9 @@ function start_rabbitmq --network "$docker_network" \ --publish 5672:5672 \ --publish 15672:15672 \ - --volume "$GITHUB_WORKSPACE/projects/OAuth2Test/enabled_plugins:/etc/rabbitmq/enabled_plugins" \ - --volume "$GITHUB_WORKSPACE/projects/OAuth2Test/$mode/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf:ro" \ - --volume "$GITHUB_WORKSPACE/projects/OAuth2Test/$mode/signing-key/signing-key.pem:/etc/rabbitmq/signing-key.pem:ro" \ + --volume "$GITHUB_WORKSPACE/projects/Test/OAuth2/enabled_plugins:/etc/rabbitmq/enabled_plugins" \ + --volume "$GITHUB_WORKSPACE/projects/Test/OAuth2/$mode/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf:ro" \ + --volume "$GITHUB_WORKSPACE/projects/Test/OAuth2/$mode/signing-key/signing-key.pem:/etc/rabbitmq/signing-key.pem:ro" \ rabbitmq:3-management } @@ -90,7 +90,7 @@ function start_oauth_service --publish 8080:8080 \ --env 'UAA_CONFIG_PATH=/uaa' \ --env 'JAVA_OPTS=-Djava.security.egd=file:/dev/./urandom' \ - --volume "$GITHUB_WORKSPACE/projects/OAuth2Test/uaa:/uaa" \ + --volume "$GITHUB_WORKSPACE/projects/Test/OAuth2/uaa:/uaa" \ "cloudfoundry/uaa:$uaa_image_version" else readonly keycloak_docker_name="$docker_name_prefix-keycloak" @@ -101,7 +101,7 @@ function start_oauth_service --env 'KEYCLOAK_ADMIN=admin' \ --env 'KEYCLOAK_ADMIN_PASSWORD=admin' \ --env KC_HEALTH_ENABLED=true \ - --volume "$GITHUB_WORKSPACE/projects/OAuth2Test/keycloak/import:/opt/keycloak/data/import" \ + --volume "$GITHUB_WORKSPACE/projects/Test/OAuth2/keycloak/import:/opt/keycloak/data/import" \ "quay.io/keycloak/keycloak:$keycloak_image_version" start-dev --metrics-enabled=true --import-realm fi } diff --git a/.ci/oauth2/test.sh b/.ci/oauth2/test.sh index e99f5449fa..eda1f159b6 100755 --- a/.ci/oauth2/test.sh +++ b/.ci/oauth2/test.sh @@ -12,4 +12,4 @@ source "$script_dir/common.sh" export OAUTH2_MODE="$mode" -dotnet test --environment OAUTH2_MODE="$mode" "$GITHUB_WORKSPACE/projects/OAuth2Test/OAuth2Test.csproj" --logger "console;verbosity=detailed" --framework "net6.0" +dotnet test --environment OAUTH2_MODE="$mode" "$GITHUB_WORKSPACE/projects/Test/OAuth2/OAuth2.csproj" --logger "console;verbosity=detailed" diff --git a/.ci/ubuntu/rabbitmq.conf b/.ci/ubuntu/rabbitmq.conf index ba2a758c03..af3f1224b1 100644 --- a/.ci/ubuntu/rabbitmq.conf +++ b/.ci/ubuntu/rabbitmq.conf @@ -1,6 +1,9 @@ log.console = false +log.exchange = false log.file = /var/log/rabbitmq/rabbitmq.log -log.file.level = debug +log.file.level = info +log.connection.level = warning +log.channel.level = warning listeners.tcp.default = 5672 listeners.ssl.default = 5671 reverse_dns_lookups = false diff --git a/.ci/windows/gha-run-tests.ps1 b/.ci/windows/gha-run-tests.ps1 deleted file mode 100644 index d75f977e57..0000000000 --- a/.ci/windows/gha-run-tests.ps1 +++ /dev/null @@ -1,39 +0,0 @@ -$ProgressPreference = 'Continue' -$ErrorActionPreference = 'Stop' -Set-StrictMode -Version 2.0 - -$erlang_reg_path = 'HKLM:\SOFTWARE\Ericsson\Erlang' -if (Test-Path 'HKLM:\SOFTWARE\WOW6432Node\') -{ - $erlang_reg_path = 'HKLM:\SOFTWARE\WOW6432Node\Ericsson\Erlang' -} -$erlang_erts_version = Get-ChildItem -Path $erlang_reg_path -Name -$erlang_home = (Get-ItemProperty -LiteralPath $erlang_reg_path\$erlang_erts_version).'(default)' - -Write-Host "[INFO] Setting ERLANG_HOME to '$erlang_home'..." -$env:ERLANG_HOME = $erlang_home -[Environment]::SetEnvironmentVariable('ERLANG_HOME', $erlang_home, 'Machine') - -$rabbitmq_base_path = (Get-ItemProperty -Name Install_Dir -Path 'HKLM:\SOFTWARE\WOW6432Node\VMware, Inc.\RabbitMQ Server').Install_Dir -$regPath = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\RabbitMQ' -if (Test-Path 'HKLM:\SOFTWARE\WOW6432Node\') -{ - $regPath = 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\RabbitMQ' -} -$rabbitmq_version = (Get-ItemProperty $regPath "DisplayVersion").DisplayVersion -$rabbitmqctl_path = Resolve-Path -LiteralPath (Join-Path -Path $rabbitmq_base_path -ChildPath "rabbitmq_server-$rabbitmq_version" | Join-Path -ChildPath 'sbin' | Join-Path -ChildPath 'rabbitmqctl.bat') - -Write-Host "[INFO] Setting RABBITMQ_RABBITMQCTL_PATH to '$rabbitmqctl_path'..." -$env:RABBITMQ_RABBITMQCTL_PATH = $rabbitmqctl_path -[Environment]::SetEnvironmentVariable('RABBITMQ_RABBITMQCTL_PATH', $rabbitmqctl_path, 'Machine') - -New-Variable -Name ci_dir -Option Constant -Value (Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath '.ci') -New-Variable -Name certs_dir -Option Constant -Value (Join-Path -Path $ci_dir -ChildPath 'certs') - -$csproj_file = Resolve-Path -LiteralPath (Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath 'projects' | Join-Path -ChildPath 'Unit' | Join-Path -ChildPath 'Unit.csproj') - -dotnet test --environment "RABBITMQ_RABBITMQCTL_PATH=$rabbitmqctl_path" ` - --environment 'RABBITMQ_LONG_RUNNING_TESTS=true' ` - --environment 'PASSWORD=grapefruit' ` - --environment "SSL_CERTS_DIR=$certs_dir" ` - $csproj_file --no-restore --no-build --logger "console;verbosity=detailed" diff --git a/.ci/windows/gha-setup.ps1 b/.ci/windows/gha-setup.ps1 index 605b851250..2f1cf929c2 100644 --- a/.ci/windows/gha-setup.ps1 +++ b/.ci/windows/gha-setup.ps1 @@ -65,6 +65,7 @@ $erlang_home = (Get-ItemProperty -LiteralPath $erlang_reg_path\$erlang_erts_vers Write-Host "[INFO] Setting ERLANG_HOME to '$erlang_home'..." $env:ERLANG_HOME = $erlang_home [Environment]::SetEnvironmentVariable('ERLANG_HOME', $erlang_home, 'Machine') +Add-Content -Verbose -LiteralPath $env:GITHUB_ENV -Value "ERLANG_HOME=$erlang_home" Write-Host "[INFO] Setting RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS..." $env:RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS = '-rabbitmq_stream advertised_host localhost' @@ -189,6 +190,17 @@ Write-Host '[INFO] Enabling plugins...' & $rabbitmq_plugins_path enable rabbitmq_management rabbitmq_stream rabbitmq_stream_management rabbitmq_amqp1_0 echo Q | openssl s_client -connect localhost:5671 -CAfile "$certs_dir/ca_certificate.pem" -cert "$certs_dir/client_localhost_certificate.pem" -key "$certs_dir/client_localhost_key.pem" -pass pass:grapefruit -if ($LASTEXITCODE -ne 0) { +if ($LASTEXITCODE -ne 0) +{ throw "[ERROR] 'openssl s_client' returned error: $LASTEXITCODE" } + + +$rabbitmqctl_path = Resolve-Path -LiteralPath ` + (Join-Path -Path $rabbitmq_base_path -ChildPath "rabbitmq_server-$rabbitmq_version" | Join-Path -ChildPath 'sbin' | Join-Path -ChildPath 'rabbitmqctl.bat') + +Write-Host "[INFO] Setting RABBITMQ_RABBITMQCTL_PATH to '$rabbitmqctl_path'..." +$env:RABBITMQ_RABBITMQCTL_PATH = $rabbitmqctl_path +[Environment]::SetEnvironmentVariable('RABBITMQ_RABBITMQCTL_PATH', $rabbitmqctl_path, 'Machine') +Add-Content -Verbose -LiteralPath $env:GITHUB_OUTPUT -Value "path=$rabbitmqctl_path" +Add-Content -Verbose -LiteralPath $env:GITHUB_ENV -Value "RABBITMQ_RABBITMQCTL_PATH=$rabbitmqctl_path" diff --git a/.ci/windows/rabbitmq.conf.in b/.ci/windows/rabbitmq.conf.in index 0923dbe6c7..38c0af233c 100644 --- a/.ci/windows/rabbitmq.conf.in +++ b/.ci/windows/rabbitmq.conf.in @@ -1,5 +1,8 @@ log.console = false -log.file.level = debug +log.exchange = false +log.file.level = info +log.connection.level = warning +log.channel.level = warning listeners.tcp.default = 5672 listeners.ssl.default = 5671 reverse_dns_lookups = false diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000000..a9209d0db8 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,5 @@ +# https://github.com/rabbitmq/rabbitmq-dotnet-client/commit/1713f50eb2dc52a97184f3857f70841dd55b5bef +1713f50eb2dc52a97184f3857f70841dd55b5bef +67c02d79d3ae48fea7de93c758dce91a51d14988 +# Revert the above +6b1a06bd429f395891a3230cad92e674dcbbb0d2 diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build-test.yaml index a7e4d6aa5b..4d37f03d7e 100644 --- a/.github/workflows/build-test.yaml +++ b/.github/workflows/build-test.yaml @@ -5,7 +5,7 @@ on: jobs: build-win32: - name: build/test on windows-latest + name: build, unit test on windows-latest runs-on: windows-latest # https://github.com/NuGet/Home/issues/11548 env: @@ -15,13 +15,6 @@ jobs: uses: actions/checkout@v4 with: submodules: true - - name: Cache installers - uses: actions/cache@v3 - with: - # Note: the cache path is relative to the workspace directory - # https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#using-the-cache-action - path: ~/installers - key: ${{ runner.os }}-v0-${{ hashFiles('.ci/versions.json') }} - name: Cache NuGet packages uses: actions/cache@v3 with: @@ -31,24 +24,96 @@ jobs: key: ${{ runner.os }}-v1-nuget-${{ hashFiles('**/*.csproj') }} restore-keys: | ${{ runner.os }}-v1-nuget- - - name: Install and Start RabbitMQ - run: .\.ci\windows\gha-setup.ps1 - - name: List NuGet sources - run: dotnet nuget locals all --list - name: Build (Debug) run: dotnet build ${{ github.workspace }}\Build.csproj - name: Verify - run: dotnet format ${{ github.workspace }}\RabbitMQDotNetClient.sln --verify-no-changes --verbosity=diagnostic - - name: Test - run: .\.ci\windows\gha-run-tests.ps1 + run: dotnet format ${{ github.workspace }}\RabbitMQDotNetClient.sln --no-restore --verify-no-changes --verbosity=diagnostic + - name: APIApproval Test + run: dotnet test "${{ github.workspace }}\projects\Test\Unit\Unit.csproj" --no-restore --no-build --logger 'console;verbosity=detailed' --filter='FullyQualifiedName=Test.Unit.APIApproval.Approve' + - name: Unit Tests + run: dotnet test "${{ github.workspace }}\projects\Test\Unit\Unit.csproj" --no-restore --no-build --logger 'console;verbosity=detailed' + - name: Upload Build (Debug) + uses: actions/upload-artifact@v3 + with: + name: rabbitmq-dotnet-client-build-win32 + path: | + projects/Test/Unit/bin + projects/Test/Integration/bin + projects/Test/SequentialIntegration/bin + projects/RabbitMQ.*/bin + integration-win32: + name: integration test on windows-latest + needs: build-win32 + runs-on: windows-latest + # https://github.com/NuGet/Home/issues/11548 + env: + NUGET_CERT_REVOCATION_MODE: offline + steps: + - name: Clone repository + uses: actions/checkout@v4 + with: + submodules: true + - name: Cache installers + uses: actions/cache@v3 + with: + # Note: the cache path is relative to the workspace directory + # https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#using-the-cache-action + path: ~/installers + key: ${{ runner.os }}-v0-${{ hashFiles('.ci/versions.json') }} + - name: Download Build (Debug) + uses: actions/download-artifact@v3 + with: + name: rabbitmq-dotnet-client-build-win32 + path: projects + - name: Install and Start RabbitMQ + id: install-start-rabbitmq + run: .\.ci\windows\gha-setup.ps1 + - name: Integration Tests + run: dotnet test --environment "RABBITMQ_RABBITMQCTL_PATH=${{ steps.install-start-rabbitmq.outputs.path }}" --environment 'RABBITMQ_LONG_RUNNING_TESTS=false' --environment 'PASSWORD=grapefruit' --environment SSL_CERTS_DIR="${{ github.workspace }}\.ci\certs" "${{ github.workspace }}\projects\Test\Integration\Integration.csproj" --no-restore --no-build --logger 'console;verbosity=detailed' + - name: Maybe upload RabbitMQ logs + if: failure() + uses: actions/upload-artifact@v3 + with: + name: rabbitmq-logs-integration-win32 + path: ~/AppData/Roaming/RabbitMQ/log/ + sequential-integration-win32: + name: sequential integration test on windows-latest + needs: build-win32 + runs-on: windows-latest + # https://github.com/NuGet/Home/issues/11548 + env: + NUGET_CERT_REVOCATION_MODE: offline + steps: + - name: Clone repository + uses: actions/checkout@v4 + with: + submodules: true + - name: Cache installers + uses: actions/cache@v3 + with: + # Note: the cache path is relative to the workspace directory + # https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#using-the-cache-action + path: ~/installers + key: ${{ runner.os }}-v0-${{ hashFiles('.ci/versions.json') }} + - name: Download Build (Debug) + uses: actions/download-artifact@v3 + with: + name: rabbitmq-dotnet-client-build-win32 + path: projects + - name: Install and Start RabbitMQ + id: install-start-rabbitmq + run: .\.ci\windows\gha-setup.ps1 + - name: Sequential Integration Tests + run: dotnet test --environment "RABBITMQ_RABBITMQCTL_PATH=${{ steps.install-start-rabbitmq.outputs.path }}" "${{ github.workspace }}\projects\Test\SequentialIntegration\SequentialIntegration.csproj" --no-restore --no-build --logger 'console;verbosity=detailed' - name: Maybe upload RabbitMQ logs if: failure() uses: actions/upload-artifact@v3 with: - name: rabbitmq-logs + name: rabbitmq-logs-sequential-integration-win32 path: ~/AppData/Roaming/RabbitMQ/log/ - build: - name: build/test on ubuntu-latest + + build-ubuntu: + name: build, unit test on ubuntu-latest runs-on: ubuntu-latest steps: - name: Clone repository @@ -68,26 +133,87 @@ jobs: key: ${{ runner.os }}-v1-nuget-${{ hashFiles('**/*.csproj') }} restore-keys: | ${{ runner.os }}-v1-nuget- - - name: Start RabbitMQ - id: start-rabbitmq - run: ${{ github.workspace }}/.ci/ubuntu/gha-setup.sh - - name: List NuGet sources - run: dotnet nuget locals all --list - name: Build (Debug) run: dotnet build ${{ github.workspace }}/Build.csproj - name: Verify - run: dotnet format ${{ github.workspace }}/RabbitMQDotNetClient.sln --verify-no-changes --verbosity=diagnostic - - name: Test + run: dotnet format ${{ github.workspace }}/RabbitMQDotNetClient.sln --no-restore --verify-no-changes --verbosity=diagnostic + - name: APIApproval Test + run: dotnet test "${{ github.workspace }}/projects/Test/Unit/Unit.csproj" --no-restore --no-build --logger 'console;verbosity=detailed' --filter='FullyQualifiedName=Test.Unit.APIApproval.Approve' + - name: Unit Tests + run: dotnet test "${{ github.workspace }}/projects/Test/Unit/Unit.csproj" --no-restore --no-build --verbosity=diagnostic --logger 'console;verbosity=detailed' + - name: Upload Build (Debug) + uses: actions/upload-artifact@v3 + with: + name: rabbitmq-dotnet-client-build-ubuntu + path: | + projects/Test/Unit/bin + projects/Test/Integration/bin + projects/Test/SequentialIntegration/bin + projects/RabbitMQ.*/bin + integration-ubuntu: + name: integration test on ubuntu-latest + needs: build-ubuntu + runs-on: ubuntu-latest + steps: + - name: Clone repository + uses: actions/checkout@v4 + with: + submodules: true + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 6.x + - name: Download Build (Debug) + uses: actions/download-artifact@v3 + with: + name: rabbitmq-dotnet-client-build-ubuntu + path: projects + - name: Start RabbitMQ + id: start-rabbitmq + run: ${{ github.workspace }}/.ci/ubuntu/gha-setup.sh + - name: Integration Tests run: | dotnet test \ --environment "RABBITMQ_RABBITMQCTL_PATH=DOCKER:${{ steps.start-rabbitmq.outputs.id }}" \ - --environment 'RABBITMQ_LONG_RUNNING_TESTS=true' \ + --environment 'RABBITMQ_LONG_RUNNING_TESTS=false' \ --environment 'PASSWORD=grapefruit' \ --environment SSL_CERTS_DIR="${{ github.workspace }}/.ci/certs" \ - "${{ github.workspace }}/projects/Unit/Unit.csproj" --no-restore --no-build --logger 'console;verbosity=detailed' --framework 'net6.0' + "${{ github.workspace }}/projects/Test/Integration/Integration.csproj" --no-restore --no-build --logger 'console;verbosity=detailed' + - name: Maybe upload RabbitMQ logs + if: failure() + uses: actions/upload-artifact@v3 + with: + name: rabbitmq-logs-integration-ubuntu + path: ${{ github.workspace }}/.ci/ubuntu/log/ + sequential-integration-ubuntu: + name: sequential integration test on ubuntu-latest + needs: build-ubuntu + runs-on: ubuntu-latest + steps: + - name: Clone repository + uses: actions/checkout@v4 + with: + submodules: true + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 6.x + - name: Download Build (Debug) + uses: actions/download-artifact@v3 + with: + name: rabbitmq-dotnet-client-build-ubuntu + path: projects + - name: Start RabbitMQ + id: start-rabbitmq + run: ${{ github.workspace }}/.ci/ubuntu/gha-setup.sh + - name: Sequential Integration Tests + run: | + dotnet test \ + --environment "RABBITMQ_RABBITMQCTL_PATH=DOCKER:${{ steps.start-rabbitmq.outputs.id }}" \ + "${{ github.workspace }}/projects/Test/SequentialIntegration/SequentialIntegration.csproj" --no-restore --no-build --logger 'console;verbosity=detailed' - name: Maybe upload RabbitMQ logs if: failure() uses: actions/upload-artifact@v3 with: - name: rabbitmq-logs + name: rabbitmq-logs-sequential-integration-ubuntu path: ${{ github.workspace }}/.ci/ubuntu/log/ diff --git a/.gitignore b/.gitignore index 19a14ef6f4..ff820f3350 100644 --- a/.gitignore +++ b/.gitignore @@ -52,8 +52,8 @@ build/ BenchmarkDotNet.Artifacts/* -projects/Unit/APIApproval.Approve.received.txt -projects/Unit/APIApproval.Approve.*.received.txt +projects/Test/Unit/APIApproval.Approve.received.txt +projects/Test/Unit/APIApproval.Approve.*.received.txt # Visual Studio 2015 cache/options directory .vs/ @@ -115,7 +115,7 @@ UpgradeLog*.htm # Unit tests -projects/Unit*/TestResult.xml +projects/Test/Unit*/TestResult.xml # Development scripts diff --git a/Build.csproj b/Build.csproj index b4c88b63a2..84aa80559a 100644 --- a/Build.csproj +++ b/Build.csproj @@ -9,10 +9,13 @@ - - - - + + + + + + + diff --git a/RUNNING_TESTS.md b/RUNNING_TESTS.md index a25a008221..f8b398ede4 100644 --- a/RUNNING_TESTS.md +++ b/RUNNING_TESTS.md @@ -71,7 +71,7 @@ in this example, it should be `./rabbitmq-server/deps/rabbit/sbin/rabbitmqctl`. It is possible to override the location using `RABBITMQ_RABBITMQCTL_PATH`: ``` -RABBITMQ_RABBITMQCTL_PATH=/path/to/rabbitmqctl dotnet test projects/Unit +RABBITMQ_RABBITMQCTL_PATH=/path/to/rabbitmqctl dotnet test projects/Test/Unit.csproj ``` ### Option Three: Using a Docker Container @@ -110,9 +110,9 @@ Running individual tests and fixtures on Windows is trivial using the Visual Stu To run a specific tests fixture on MacOS or Linux, use the NUnit filter expressions to select the tests to be run: ``` shell -dotnet test projects/Unit --filter "Name~TestAmqpUriParseFail" +dotnet test projects/Test/Unit.csproj --filter "Name~TestAmqpUriParseFail" -dotnet test projects/Unit --filter "FullyQualifiedName~RabbitMQ.Client.Unit.TestHeartbeats" +dotnet test projects/Test/Unit.csproj --filter "FullyQualifiedName~RabbitMQ.Client.Unit.TestHeartbeats" ``` ## Running Tests for a Specific .NET Target diff --git a/RabbitMQDotNetClient.sln b/RabbitMQDotNetClient.sln index 4d22b5e523..04d32e7562 100644 --- a/RabbitMQDotNetClient.sln +++ b/RabbitMQDotNetClient.sln @@ -9,19 +9,33 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RabbitMQ.Client", "projects\RabbitMQ.Client\RabbitMQ.Client.csproj", "{8C554257-5ECC-45DB-873D-560BFBB74EC8}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Unit", "projects\Unit\Unit.csproj", "{B8FAC024-CC03-4067-9FFC-02846FB8AE48}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Unit", "projects\Test\Unit\Unit.csproj", "{B8FAC024-CC03-4067-9FFC-02846FB8AE48}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks", "projects\Benchmarks\Benchmarks.csproj", "{38D72C9A-68E9-4653-B0CE-C7BA9FFD91D0}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MassPublish", "projects\TestApplications\MassPublish\MassPublish.csproj", "{0E3C4FBE-9976-40A3-9F57-DC0D9B7A39A6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MassPublish", "projects\Test\Applications\MassPublish\MassPublish.csproj", "{0E3C4FBE-9976-40A3-9F57-DC0D9B7A39A6}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestApplications", "TestApplications", "{D21B282C-49E6-4A30-887B-9626D94B8D69}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Applications", "Applications", "{D21B282C-49E6-4A30-887B-9626D94B8D69}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CreateChannel", "projects\TestApplications\CreateChannel\CreateChannel.csproj", "{4A589408-F3A3-40E1-A6DF-F5E620F7CA31}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CreateChannel", "projects\Test\Applications\CreateChannel\CreateChannel.csproj", "{4A589408-F3A3-40E1-A6DF-F5E620F7CA31}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RabbitMQ.Client.OAuth2", "projects\RabbitMQ.Client.OAuth2\RabbitMQ.Client.OAuth2.csproj", "{794C7B31-0E9A-44A4-B285-0F3CAF6209F1}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OAuth2Test", "projects\OAuth2Test\OAuth2Test.csproj", "{897D13F0-AF06-444A-9072-CF7E809A4A2C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OAuth2", "projects\Test\OAuth2\OAuth2.csproj", "{897D13F0-AF06-444A-9072-CF7E809A4A2C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{EFD4BED5-13A5-4D9C-AADF-CAB7E1573704}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Integration", "projects\Test\Integration\Integration.csproj", "{B01347D8-C327-471B-A1FE-7B86F7684A27}" + ProjectSection(ProjectDependencies) = postProject + {C11F25F4-7EA1-4874-9E25-DEB42E3A7C67} = {C11F25F4-7EA1-4874-9E25-DEB42E3A7C67} + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SequentialIntegration", "projects\Test\SequentialIntegration\SequentialIntegration.csproj", "{F25725D7-2978-45F4-B90F-25D6F8B71C9E}" + ProjectSection(ProjectDependencies) = postProject + {C11F25F4-7EA1-4874-9E25-DEB42E3A7C67} = {C11F25F4-7EA1-4874-9E25-DEB42E3A7C67} + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common", "projects\Test\Common\Common.csproj", "{C11F25F4-7EA1-4874-9E25-DEB42E3A7C67}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -57,13 +71,31 @@ Global {897D13F0-AF06-444A-9072-CF7E809A4A2C}.Debug|Any CPU.Build.0 = Debug|Any CPU {897D13F0-AF06-444A-9072-CF7E809A4A2C}.Release|Any CPU.ActiveCfg = Release|Any CPU {897D13F0-AF06-444A-9072-CF7E809A4A2C}.Release|Any CPU.Build.0 = Release|Any CPU + {B01347D8-C327-471B-A1FE-7B86F7684A27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B01347D8-C327-471B-A1FE-7B86F7684A27}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B01347D8-C327-471B-A1FE-7B86F7684A27}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B01347D8-C327-471B-A1FE-7B86F7684A27}.Release|Any CPU.Build.0 = Release|Any CPU + {F25725D7-2978-45F4-B90F-25D6F8B71C9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F25725D7-2978-45F4-B90F-25D6F8B71C9E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F25725D7-2978-45F4-B90F-25D6F8B71C9E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F25725D7-2978-45F4-B90F-25D6F8B71C9E}.Release|Any CPU.Build.0 = Release|Any CPU + {C11F25F4-7EA1-4874-9E25-DEB42E3A7C67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C11F25F4-7EA1-4874-9E25-DEB42E3A7C67}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C11F25F4-7EA1-4874-9E25-DEB42E3A7C67}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C11F25F4-7EA1-4874-9E25-DEB42E3A7C67}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution + {B8FAC024-CC03-4067-9FFC-02846FB8AE48} = {EFD4BED5-13A5-4D9C-AADF-CAB7E1573704} {0E3C4FBE-9976-40A3-9F57-DC0D9B7A39A6} = {D21B282C-49E6-4A30-887B-9626D94B8D69} + {D21B282C-49E6-4A30-887B-9626D94B8D69} = {EFD4BED5-13A5-4D9C-AADF-CAB7E1573704} {4A589408-F3A3-40E1-A6DF-F5E620F7CA31} = {D21B282C-49E6-4A30-887B-9626D94B8D69} + {897D13F0-AF06-444A-9072-CF7E809A4A2C} = {EFD4BED5-13A5-4D9C-AADF-CAB7E1573704} + {B01347D8-C327-471B-A1FE-7B86F7684A27} = {EFD4BED5-13A5-4D9C-AADF-CAB7E1573704} + {F25725D7-2978-45F4-B90F-25D6F8B71C9E} = {EFD4BED5-13A5-4D9C-AADF-CAB7E1573704} + {C11F25F4-7EA1-4874-9E25-DEB42E3A7C67} = {EFD4BED5-13A5-4D9C-AADF-CAB7E1573704} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3C6A0C44-FA63-4101-BBF9-2598641167D1} diff --git a/build.ps1 b/build.ps1 index 9b630e8011..caae1b570a 100644 --- a/build.ps1 +++ b/build.ps1 @@ -14,7 +14,7 @@ Write-Host "Done building." -ForegroundColor "Green" if ($RunTests) { - $unit_csproj_file = Resolve-Path -LiteralPath (Join-Path -Path $PSScriptRoot -ChildPath 'projects' | Join-Path -ChildPath 'Unit' | Join-Path -ChildPath 'Unit.csproj') + $unit_csproj_file = Resolve-Path -LiteralPath (Join-Path -Path $PSScriptRoot -ChildPath 'projects' | Join-Path -ChildPath 'Test' | Join-Path -ChildPath 'Unit' | Join-Path -ChildPath 'Unit.csproj') Write-Host "Running Unit / Integration tests from '$unit_csproj_file' (all frameworks)" -ForegroundColor "Magenta" dotnet test $unit_csproj_file --no-restore --no-build --logger "console;verbosity=detailed" if ($LastExitCode -ne 0) { diff --git a/projects/RabbitMQ.Client/RabbitMQ.Client.csproj b/projects/RabbitMQ.Client/RabbitMQ.Client.csproj index 68ebb42a02..d591d70b0a 100644 --- a/projects/RabbitMQ.Client/RabbitMQ.Client.csproj +++ b/projects/RabbitMQ.Client/RabbitMQ.Client.csproj @@ -45,9 +45,18 @@ + + <_Parameter1>Common, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d20ec856aeeb8c3153a77faa2d80e6e43b5db93224a20cc7ae384f65f142e89730e2ff0fcc5d578bbe96fa98a7196c77329efdee4579b3814c0789e5a39b51df6edd75b602a33ceabdfcf19a3feb832f31d8254168cd7ba5700dfbca301fbf8db614ba41ba18474de0a5f4c2d51c995bc3636c641c8cbe76f45717bfcb943b5 + <_Parameter1>Unit, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d20ec856aeeb8c3153a77faa2d80e6e43b5db93224a20cc7ae384f65f142e89730e2ff0fcc5d578bbe96fa98a7196c77329efdee4579b3814c0789e5a39b51df6edd75b602a33ceabdfcf19a3feb832f31d8254168cd7ba5700dfbca301fbf8db614ba41ba18474de0a5f4c2d51c995bc3636c641c8cbe76f45717bfcb943b5 + + <_Parameter1>Integration, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d20ec856aeeb8c3153a77faa2d80e6e43b5db93224a20cc7ae384f65f142e89730e2ff0fcc5d578bbe96fa98a7196c77329efdee4579b3814c0789e5a39b51df6edd75b602a33ceabdfcf19a3feb832f31d8254168cd7ba5700dfbca301fbf8db614ba41ba18474de0a5f4c2d51c995bc3636c641c8cbe76f45717bfcb943b5 + + + <_Parameter1>SequentialIntegration, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d20ec856aeeb8c3153a77faa2d80e6e43b5db93224a20cc7ae384f65f142e89730e2ff0fcc5d578bbe96fa98a7196c77329efdee4579b3814c0789e5a39b51df6edd75b602a33ceabdfcf19a3feb832f31d8254168cd7ba5700dfbca301fbf8db614ba41ba18474de0a5f4c2d51c995bc3636c641c8cbe76f45717bfcb943b5 + <_Parameter1>Benchmarks, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d20ec856aeeb8c3153a77faa2d80e6e43b5db93224a20cc7ae384f65f142e89730e2ff0fcc5d578bbe96fa98a7196c77329efdee4579b3814c0789e5a39b51df6edd75b602a33ceabdfcf19a3feb832f31d8254168cd7ba5700dfbca301fbf8db614ba41ba18474de0a5f4c2d51c995bc3636c641c8cbe76f45717bfcb943b5 diff --git a/projects/RabbitMQ.Client/client/api/AmqpTimestamp.cs b/projects/RabbitMQ.Client/client/api/AmqpTimestamp.cs index 2465fd167b..b735d14be3 100644 --- a/projects/RabbitMQ.Client/client/api/AmqpTimestamp.cs +++ b/projects/RabbitMQ.Client/client/api/AmqpTimestamp.cs @@ -33,9 +33,6 @@ namespace RabbitMQ.Client { - // time representations in mainstream languages: the horror, the horror - // see in particular the difference between .NET 1.x and .NET 2.0's versions of DateTime - /// /// Structure holding an AMQP timestamp, a posix 64-bit time_t. /// diff --git a/projects/RabbitMQ.Client/client/api/IChannel.cs b/projects/RabbitMQ.Client/client/api/IChannel.cs index 66515e4c01..5ded578ed2 100644 --- a/projects/RabbitMQ.Client/client/api/IChannel.cs +++ b/projects/RabbitMQ.Client/client/api/IChannel.cs @@ -58,8 +58,9 @@ public interface IChannel : IDisposable /// ShutdownEventArgs CloseReason { get; } - /// Signalled when an unexpected message is delivered + /// Signalled when an unexpected message is delivered. /// + /// /// Under certain circumstances it is possible for a channel to receive a /// message delivery which does not match any consumer which is currently /// set up via basicConsume(). This will occur after the following sequence @@ -79,7 +80,8 @@ public interface IChannel : IDisposable /// such deliveries. If no default consumer is registered an /// InvalidOperationException will be thrown when such a delivery arrives. /// - /// Most people will not need to use this. + /// Most people will not need to use this. + /// IBasicConsumer DefaultConsumer { get; set; } /// @@ -89,7 +91,8 @@ public interface IChannel : IDisposable /// /// Returns true if the channel is still in a state where it can be used. - /// Identical to checking if equals null. + /// Identical to checking if equals null. + /// bool IsOpen { get; } /// @@ -115,17 +118,6 @@ public interface IChannel : IDisposable /// event EventHandler BasicNacks; - /// - /// All messages received before this fires that haven't been ack'ed will be redelivered. - /// All messages received afterwards won't be. - /// - /// - /// Handlers for this event are invoked by the connection thread. - /// It is sometimes useful to allow that thread to know that a recover-ok - /// has been received, rather than the thread that invoked . - /// - event EventHandler BasicRecoverOk; - /// /// Signalled when a Basic.Return command arrives from the broker. /// @@ -208,9 +200,32 @@ public interface IChannel : IDisposable /// BasicGetResult BasicGet(string queue, bool autoAck); - /// Reject one or more delivered message(s). + /// + /// Asynchronously retrieve an individual message, if + /// one is available; returns null if the server answers that + /// no messages are currently available. See also . + /// + /// The queue. + /// If set to true, automatically ack the message. + /// + ValueTask BasicGetAsync(string queue, bool autoAck); + + /// + /// Nack one or more delivered message(s). + /// + /// The delivery tag. + /// If set to true, nack all messages up to the current tag. + /// If set to true, requeue nack'd messages. void BasicNack(ulong deliveryTag, bool multiple, bool requeue); + /// + /// Asynchronously nack one or more delivered message(s). + /// + /// The delivery tag. + /// If set to true, nack all messages up to the current tag. + /// If set to true, requeue nack'd messages. + ValueTask BasicNackAsync(ulong deliveryTag, bool multiple, bool requeue); + #nullable enable /// @@ -277,20 +292,6 @@ ValueTask BasicPublishAsync(CachedString exchange, CachedString rou /// See the Consumer Prefetch documentation. ValueTask BasicQosAsync(uint prefetchSize, ushort prefetchCount, bool global); - /// - /// Indicates that a consumer has recovered. - /// Deprecated. Should not be used. - /// - [Obsolete] - void BasicRecover(bool requeue); - - /// - /// Indicates that a consumer has recovered. - /// Deprecated. Should not be used. - /// - [Obsolete] - void BasicRecoverAsync(bool requeue); - /// Reject a delivered message. void BasicReject(ulong deliveryTag, bool requeue); @@ -304,10 +305,19 @@ ValueTask BasicPublishAsync(CachedString exchange, CachedString rou void Close(ushort replyCode, string replyText, bool abort); /// - /// Enable publisher acknowledgements. + /// Asynchronously close this session. /// + /// The instance containing the close data. + /// Whether or not the close is an abort (ignoring certain exceptions). + /// + ValueTask CloseAsync(ShutdownEventArgs reason, bool abort); + + /// Enable publisher confirmations. void ConfirmSelect(); + /// Asynchronously enable publisher confirmations. + ValueTask ConfirmSelectAsync(); + /// /// Bind an exchange to an exchange. /// @@ -374,14 +384,6 @@ ValueTask BasicPublishAsync(CachedString exchange, CachedString rou /// void ExchangeDelete(string exchange, bool ifUnused); - /* - * TODO LRB rabbitmq/rabbitmq-dotnet-client#1347 - /// - /// Asynchronously delete an exchange. - /// - ValueTask ExchangeDeleteAsync(string exchange, bool ifUnused); - */ - /// /// Asynchronously delete an exchange. /// @@ -421,20 +423,24 @@ ValueTask BasicPublishAsync(CachedString exchange, CachedString rou /// /// Bind a queue to an exchange. /// + /// The queue. + /// The exchange. + /// The routing key. + /// The arguments. /// - /// - /// Routing key must be shorter than 255 bytes. - /// + /// Routing key must be shorter than 255 bytes. /// void QueueBind(string queue, string exchange, string routingKey, IDictionary arguments); /// /// Asynchronously bind a queue to an exchange. /// + /// The queue. + /// The exchange. + /// The routing key. + /// The arguments. /// - /// - /// Routing key must be shorter than 255 bytes. - /// + /// Routing key must be shorter than 255 bytes. /// ValueTask QueueBindAsync(string queue, string exchange, string routingKey, IDictionary arguments); @@ -528,39 +534,58 @@ ValueTask BasicPublishAsync(CachedString exchange, CachedString rou /// Returns the number of messages purged during deletion. void QueueDeleteNoWait(string queue, bool ifUnused, bool ifEmpty); - /// - /// Purge a queue of messages. - /// - /// - /// Returns the number of messages purged. - /// + /// Asynchronously purge a queue of messages. + /// The queue. + /// Returns the number of messages purged. uint QueuePurge(string queue); + /// Asynchronously purge a queue of messages. + /// The queue. + /// Returns the number of messages purged. + ValueTask QueuePurgeAsync(string queue); + /// /// Unbind a queue from an exchange. /// + /// The queue. + /// The exchange. + /// The routing key. + /// The arguments. /// - /// - /// Routing key must be shorter than 255 bytes. - /// + /// Routing key must be shorter than 255 bytes. /// void QueueUnbind(string queue, string exchange, string routingKey, IDictionary arguments); /// - /// Commit this session's active TX transaction. + /// Asynchronously unbind a queue from an exchange. /// + /// The queue. + /// The exchange. + /// The routing key. + /// The arguments. + /// + /// Routing key must be shorter than 255 bytes. + /// + ValueTask QueueUnbindAsync(string queue, string exchange, string routingKey, IDictionary arguments); + + /// Commit this session's active TX transaction. void TxCommit(); - /// - /// Roll back this session's active TX transaction. - /// + /// Asynchronously commit this session's active TX transaction. + ValueTask TxCommitAsync(); + + /// Roll back this session's active TX transaction. void TxRollback(); - /// - /// Enable TX mode for this session. - /// + /// Asynchronously roll back this session's active TX transaction. + ValueTask TxRollbackAsync(); + + /// Enable TX mode for this session. void TxSelect(); + /// Asynchronously enable TX mode for this session. + ValueTask TxSelectAsync(); + /// /// Wait until all published messages on this channel have been confirmed. /// diff --git a/projects/RabbitMQ.Client/client/api/IChannelExtensions.cs b/projects/RabbitMQ.Client/client/api/IChannelExtensions.cs index 6beb1d6bb5..22b7463fe5 100644 --- a/projects/RabbitMQ.Client/client/api/IChannelExtensions.cs +++ b/projects/RabbitMQ.Client/client/api/IChannelExtensions.cs @@ -250,15 +250,32 @@ public static void Close(this IChannel channel) channel.Close(Constants.ReplySuccess, "Goodbye", false); } - /// Close this session. + /// Asynchronously close this session. + /// + /// If the session is already closed (or closing), then this + /// method does nothing but wait for the in-progress close + /// operation to complete. This method will not return to the + /// caller until the shutdown is complete. + /// + public static ValueTask CloseAsync(this IChannel channel) + { + var reason = new ShutdownEventArgs(ShutdownInitiator.Library, Constants.ReplySuccess, "Goodbye"); + return channel.CloseAsync(reason, false); + } + + /// + /// Close this channel. + /// + /// The channel. + /// The reply code. + /// The reply text. /// /// The method behaves in the same way as Close(), with the only /// difference that the channel is closed with the given channel /// close code and message. /// /// The close code (See under "Reply Codes" in the AMQP specification) - /// - /// + /// /// A message indicating the reason for closing the channel /// /// diff --git a/projects/RabbitMQ.Client/client/framing/Channel.cs b/projects/RabbitMQ.Client/client/framing/Channel.cs index d0930b7aef..4ed5678c0c 100644 --- a/projects/RabbitMQ.Client/client/framing/Channel.cs +++ b/projects/RabbitMQ.Client/client/framing/Channel.cs @@ -62,11 +62,6 @@ public override void _Private_BasicGet(string queue, bool autoAck) ChannelSend(new BasicGet(queue, autoAck)); } - public override void _Private_BasicRecover(bool requeue) - { - ChannelSend(new BasicRecover(requeue)); - } - public override void _Private_ChannelClose(ushort replyCode, string replyText, ushort classId, ushort methodId) { ChannelSend(new ChannelClose(replyCode, replyText, classId, methodId)); @@ -245,14 +240,15 @@ public override void BasicNack(ulong deliveryTag, bool multiple, bool requeue) ChannelSend(new BasicNack(deliveryTag, multiple, requeue)); } - public override void BasicQos(uint prefetchSize, ushort prefetchCount, bool global) + public override ValueTask BasicNackAsync(ulong deliveryTag, bool multiple, bool requeue) { - ChannelRpc(new BasicQos(prefetchSize, prefetchCount, global), ProtocolCommandId.BasicQosOk); + var method = new BasicNack(deliveryTag, multiple, requeue); + return ModelSendAsync(method); } - public override void BasicRecoverAsync(bool requeue) + public override void BasicQos(uint prefetchSize, ushort prefetchCount, bool global) { - ChannelSend(new BasicRecoverAsync(requeue)); + ChannelRpc(new BasicQos(prefetchSize, prefetchCount, global), ProtocolCommandId.BasicQosOk); } public override void BasicReject(ulong deliveryTag, bool requeue) @@ -321,20 +317,13 @@ protected override bool DispatchAsynchronous(in IncomingCommand cmd) } case ProtocolCommandId.BasicGetOk: { - HandleBasicGetOk(in cmd); - return true; + return HandleBasicGetOk(in cmd); } case ProtocolCommandId.BasicNack: { HandleBasicNack(in cmd); return true; } - case ProtocolCommandId.BasicRecoverOk: - { - cmd.ReturnMethodBuffer(); - HandleBasicRecoverOk(); - return true; - } case ProtocolCommandId.BasicReturn: { HandleBasicReturn(in cmd); @@ -347,8 +336,7 @@ protected override bool DispatchAsynchronous(in IncomingCommand cmd) } case ProtocolCommandId.ChannelCloseOk: { - cmd.ReturnMethodBuffer(); - HandleChannelCloseOk(); + HandleChannelCloseOk(in cmd); return true; } case ProtocolCommandId.ChannelFlow: diff --git a/projects/RabbitMQ.Client/client/impl/AsyncRpcContinuations.cs b/projects/RabbitMQ.Client/client/impl/AsyncRpcContinuations.cs index a171515dce..62ea0778ab 100644 --- a/projects/RabbitMQ.Client/client/impl/AsyncRpcContinuations.cs +++ b/projects/RabbitMQ.Client/client/impl/AsyncRpcContinuations.cs @@ -68,7 +68,10 @@ public AsyncRpcContinuation(TimeSpan continuationTimeout) // What to do if setting a result fails? public abstract void HandleCommand(in IncomingCommand cmd); - public void HandleChannelShutdown(ShutdownEventArgs reason) => _tcs.SetException(new OperationInterruptedException(reason)); + public virtual void HandleChannelShutdown(ShutdownEventArgs reason) + { + _tcs.SetException(new OperationInterruptedException(reason)); + } protected virtual void Dispose(bool disposing) { @@ -198,6 +201,51 @@ public override void HandleCommand(in IncomingCommand cmd) } } + internal class BasicGetAsyncRpcContinuation : AsyncRpcContinuation + { + private readonly Func _adjustDeliveryTag; + + public BasicGetAsyncRpcContinuation(Func adjustDeliveryTag, TimeSpan continuationTimeout) + : base(continuationTimeout) + { + _adjustDeliveryTag = adjustDeliveryTag; + } + + public override void HandleCommand(in IncomingCommand cmd) + { + try + { + if (cmd.CommandId == ProtocolCommandId.BasicGetOk) + { + var method = new Client.Framing.Impl.BasicGetOk(cmd.MethodBytes.Span); + cmd.ReturnMethodBuffer(); + var header = new ReadOnlyBasicProperties(cmd.HeaderBytes.Span); + cmd.ReturnHeaderBuffer(); + + var result = new BasicGetResult( + _adjustDeliveryTag(method._deliveryTag), + method._redelivered, + method._exchange, + method._routingKey, + method._messageCount, + header, + cmd.Body, + cmd.TakeoverBody()); + + _tcs.TrySetResult(result); + } + else + { + _tcs.SetException(new InvalidOperationException($"Received unexpected command of type {cmd.CommandId}!")); + } + } + finally + { + cmd.ReturnMethodBuffer(); + } + } + } + internal class BasicQosAsyncRpcContinuation : SimpleAsyncRpcContinuation { public BasicQosAsyncRpcContinuation(TimeSpan continuationTimeout) @@ -206,6 +254,32 @@ public BasicQosAsyncRpcContinuation(TimeSpan continuationTimeout) } } + internal class ChannelCloseAsyncRpcContinuation : SimpleAsyncRpcContinuation + { + public ChannelCloseAsyncRpcContinuation(TimeSpan continuationTimeout) + : base(ProtocolCommandId.ChannelCloseOk, continuationTimeout) + { + } + + public override void HandleChannelShutdown(ShutdownEventArgs reason) + { + // Nothing to do here! + } + + public void OnConnectionShutdown(object sender, ShutdownEventArgs reason) + { + _tcs.TrySetResult(true); + } + } + + internal class ConfirmSelectAsyncRpcContinuation : SimpleAsyncRpcContinuation + { + public ConfirmSelectAsyncRpcContinuation(TimeSpan continuationTimeout) + : base(ProtocolCommandId.ConfirmSelectOk, continuationTimeout) + { + } + } + internal class ExchangeBindAsyncRpcContinuation : SimpleAsyncRpcContinuation { public ExchangeBindAsyncRpcContinuation(TimeSpan continuationTimeout) @@ -274,6 +348,14 @@ public QueueBindAsyncRpcContinuation(TimeSpan continuationTimeout) } } + internal class QueueUnbindAsyncRpcContinuation : SimpleAsyncRpcContinuation + { + public QueueUnbindAsyncRpcContinuation(TimeSpan continuationTimeout) + : base(ProtocolCommandId.QueueUnbindOk, continuationTimeout) + { + } + } + internal class QueueDeleteAsyncRpcContinuation : AsyncRpcContinuation { public QueueDeleteAsyncRpcContinuation(TimeSpan continuationTimeout) : base(continuationTimeout) @@ -300,4 +382,55 @@ public override void HandleCommand(in IncomingCommand cmd) } } } + + internal class QueuePurgeAsyncRpcContinuation : AsyncRpcContinuation + { + public QueuePurgeAsyncRpcContinuation(TimeSpan continuationTimeout) : base(continuationTimeout) + { + } + + public override void HandleCommand(in IncomingCommand cmd) + { + try + { + if (cmd.CommandId == ProtocolCommandId.QueuePurgeOk) + { + var method = new Client.Framing.Impl.QueuePurgeOk(cmd.MethodBytes.Span); + _tcs.TrySetResult(method._messageCount); + } + else + { + _tcs.SetException(new InvalidOperationException($"Received unexpected command of type {cmd.CommandId}!")); + } + } + finally + { + cmd.ReturnMethodBuffer(); + } + } + } + + internal class TxCommitAsyncRpcContinuation : SimpleAsyncRpcContinuation + { + public TxCommitAsyncRpcContinuation(TimeSpan continuationTimeout) + : base(ProtocolCommandId.TxCommitOk, continuationTimeout) + { + } + } + + internal class TxRollbackAsyncRpcContinuation : SimpleAsyncRpcContinuation + { + public TxRollbackAsyncRpcContinuation(TimeSpan continuationTimeout) + : base(ProtocolCommandId.TxRollbackOk, continuationTimeout) + { + } + } + + internal class TxSelectAsyncRpcContinuation : SimpleAsyncRpcContinuation + { + public TxSelectAsyncRpcContinuation(TimeSpan continuationTimeout) + : base(ProtocolCommandId.TxSelectOk, continuationTimeout) + { + } + } } diff --git a/projects/RabbitMQ.Client/client/impl/AutorecoveringChannel.cs b/projects/RabbitMQ.Client/client/impl/AutorecoveringChannel.cs index aac065c7a2..a9d9d0c77e 100644 --- a/projects/RabbitMQ.Client/client/impl/AutorecoveringChannel.cs +++ b/projects/RabbitMQ.Client/client/impl/AutorecoveringChannel.cs @@ -87,12 +87,6 @@ public event EventHandler BasicNacks remove => InnerChannel.BasicNacks -= value; } - public event EventHandler BasicRecoverOk - { - add => InnerChannel.BasicRecoverOk += value; - remove => InnerChannel.BasicRecoverOk -= value; - } - public event EventHandler BasicReturn { add => InnerChannel.BasicReturn += value; @@ -251,6 +245,19 @@ public void Close(ushort replyCode, string replyText, bool abort) } } + public async ValueTask CloseAsync(ShutdownEventArgs reason, bool abort) + { + ThrowIfDisposed(); + try + { + await _innerChannel.CloseAsync(reason, abort); + } + finally + { + _connection.DeleteRecordedChannel(this); + } + } + public override string ToString() => InnerChannel.ToString(); @@ -271,16 +278,10 @@ public void Dispose() } public void BasicAck(ulong deliveryTag, bool multiple) - { - ThrowIfDisposed(); - _innerChannel.BasicAck(deliveryTag, multiple); - } + => InnerChannel.BasicAck(deliveryTag, multiple); public ValueTask BasicAckAsync(ulong deliveryTag, bool multiple) - { - ThrowIfDisposed(); - return _innerChannel.BasicAckAsync(deliveryTag, multiple); - } + => InnerChannel.BasicAckAsync(deliveryTag, multiple); public void BasicCancel(string consumerTag) { @@ -328,9 +329,15 @@ public async ValueTask BasicConsumeAsync(string queue, bool autoAck, str public BasicGetResult BasicGet(string queue, bool autoAck) => InnerChannel.BasicGet(queue, autoAck); + public ValueTask BasicGetAsync(string queue, bool autoAck) + => InnerChannel.BasicGetAsync(queue, autoAck); + public void BasicNack(ulong deliveryTag, bool multiple, bool requeue) => InnerChannel.BasicNack(deliveryTag, multiple, requeue); + public ValueTask BasicNackAsync(ulong deliveryTag, bool multiple, bool requeue) + => InnerChannel.BasicNackAsync(deliveryTag, multiple, requeue); + public void BasicPublish(string exchange, string routingKey, in TProperties basicProperties, ReadOnlyMemory body, bool mandatory) where TProperties : IReadOnlyBasicProperties, IAmqpHeader => InnerChannel.BasicPublish(exchange, routingKey, in basicProperties, body, mandatory); @@ -379,12 +386,6 @@ public ValueTask BasicQosAsync(uint prefetchSize, ushort prefetchCount, bool glo return _innerChannel.BasicQosAsync(prefetchSize, prefetchCount, global); } - public void BasicRecover(bool requeue) - => InnerChannel.BasicRecover(requeue); - - public void BasicRecoverAsync(bool requeue) - => InnerChannel.BasicRecoverAsync(requeue); - public void BasicReject(ulong deliveryTag, bool requeue) => InnerChannel.BasicReject(deliveryTag, requeue); @@ -397,6 +398,13 @@ public void ConfirmSelect() _usesPublisherConfirms = true; } + public ValueTask ConfirmSelectAsync() + { + var task = InnerChannel.ConfirmSelectAsync(); + _usesPublisherConfirms = true; + return task; + } + public void ExchangeBind(string destination, string source, string routingKey, IDictionary arguments) { ThrowIfDisposed(); @@ -406,8 +414,7 @@ public void ExchangeBind(string destination, string source, string routingKey, I public async ValueTask ExchangeBindAsync(string destination, string source, string routingKey, IDictionary arguments) { - ThrowIfDisposed(); - await _innerChannel.ExchangeBindAsync(destination, source, routingKey, arguments); + await InnerChannel.ExchangeBindAsync(destination, source, routingKey, arguments); _connection.RecordBinding(new RecordedBinding(false, destination, source, routingKey, arguments)); } @@ -416,15 +423,13 @@ public void ExchangeBindNoWait(string destination, string source, string routing public void ExchangeDeclare(string exchange, string type, bool durable, bool autoDelete, IDictionary arguments) { - ThrowIfDisposed(); - _innerChannel.ExchangeDeclare(exchange, type, durable, autoDelete, arguments); + InnerChannel.ExchangeDeclare(exchange, type, durable, autoDelete, arguments); _connection.RecordExchange(new RecordedExchange(exchange, type, durable, autoDelete, arguments)); } public async ValueTask ExchangeDeclareAsync(string exchange, string type, bool passive, bool durable, bool autoDelete, IDictionary arguments) { - ThrowIfDisposed(); - await _innerChannel.ExchangeDeclareAsync(exchange, type, passive, durable, autoDelete, arguments); + await InnerChannel.ExchangeDeclareAsync(exchange, type, passive, durable, autoDelete, arguments); if (false == passive) { _connection.RecordExchange(new RecordedExchange(exchange, type, durable, autoDelete, arguments)); @@ -433,8 +438,7 @@ public async ValueTask ExchangeDeclareAsync(string exchange, string type, bool p public void ExchangeDeclareNoWait(string exchange, string type, bool durable, bool autoDelete, IDictionary arguments) { - ThrowIfDisposed(); - _innerChannel.ExchangeDeclareNoWait(exchange, type, durable, autoDelete, arguments); + InnerChannel.ExchangeDeclareNoWait(exchange, type, durable, autoDelete, arguments); _connection.RecordExchange(new RecordedExchange(exchange, type, durable, autoDelete, arguments)); } @@ -469,8 +473,7 @@ public void ExchangeUnbind(string destination, string source, string routingKey, public async ValueTask ExchangeUnbindAsync(string destination, string source, string routingKey, IDictionary arguments) { - ThrowIfDisposed(); - await _innerChannel.ExchangeUnbindAsync(destination, source, routingKey, arguments); + await InnerChannel.ExchangeUnbindAsync(destination, source, routingKey, arguments); _connection.DeleteRecordedBinding(new RecordedBinding(false, destination, source, routingKey, arguments)); _connection.DeleteAutoDeleteExchange(source); } @@ -490,23 +493,20 @@ public void QueueBindNoWait(string queue, string exchange, string routingKey, ID public QueueDeclareOk QueueDeclare(string queue, bool durable, bool exclusive, bool autoDelete, IDictionary arguments) { - ThrowIfDisposed(); - QueueDeclareOk result = _innerChannel.QueueDeclare(queue, durable, exclusive, autoDelete, arguments); + QueueDeclareOk result = InnerChannel.QueueDeclare(queue, durable, exclusive, autoDelete, arguments); _connection.RecordQueue(new RecordedQueue(result.QueueName, queue.Length == 0, durable, exclusive, autoDelete, arguments)); return result; } public void QueueDeclareNoWait(string queue, bool durable, bool exclusive, bool autoDelete, IDictionary arguments) { - ThrowIfDisposed(); - _innerChannel.QueueDeclareNoWait(queue, durable, exclusive, autoDelete, arguments); + InnerChannel.QueueDeclareNoWait(queue, durable, exclusive, autoDelete, arguments); _connection.RecordQueue(new RecordedQueue(queue, queue.Length == 0, durable, exclusive, autoDelete, arguments)); } public async ValueTask QueueDeclareAsync(string queue, bool passive, bool durable, bool exclusive, bool autoDelete, IDictionary arguments) { - ThrowIfDisposed(); - QueueDeclareOk result = await _innerChannel.QueueDeclareAsync(queue, passive, durable, exclusive, autoDelete, arguments); + QueueDeclareOk result = await InnerChannel.QueueDeclareAsync(queue, passive, durable, exclusive, autoDelete, arguments); if (false == passive) { _connection.RecordQueue(new RecordedQueue(result.QueueName, queue.Length == 0, durable, exclusive, autoDelete, arguments)); @@ -514,11 +514,8 @@ public async ValueTask QueueDeclareAsync(string queue, bool pass return result; } - public async ValueTask QueueBindAsync(string queue, string exchange, string routingKey, IDictionary arguments) - { - ThrowIfDisposed(); - await _innerChannel.QueueBindAsync(queue, exchange, routingKey, arguments); - } + public ValueTask QueueBindAsync(string queue, string exchange, string routingKey, IDictionary arguments) + => InnerChannel.QueueBindAsync(queue, exchange, routingKey, arguments); public QueueDeclareOk QueueDeclarePassive(string queue) => InnerChannel.QueueDeclarePassive(queue); @@ -531,16 +528,14 @@ public uint ConsumerCount(string queue) public uint QueueDelete(string queue, bool ifUnused, bool ifEmpty) { - ThrowIfDisposed(); - uint result = _innerChannel.QueueDelete(queue, ifUnused, ifEmpty); + uint result = InnerChannel.QueueDelete(queue, ifUnused, ifEmpty); _connection.DeleteRecordedQueue(queue); return result; } public async ValueTask QueueDeleteAsync(string queue, bool ifUnused, bool ifEmpty) { - ThrowIfDisposed(); - uint result = await _innerChannel.QueueDeleteAsync(queue, ifUnused, ifEmpty); + uint result = await InnerChannel.QueueDeleteAsync(queue, ifUnused, ifEmpty); _connection.DeleteRecordedQueue(queue); return result; } @@ -554,6 +549,9 @@ public void QueueDeleteNoWait(string queue, bool ifUnused, bool ifEmpty) public uint QueuePurge(string queue) => InnerChannel.QueuePurge(queue); + public ValueTask QueuePurgeAsync(string queue) + => InnerChannel.QueuePurgeAsync(queue); + public void QueueUnbind(string queue, string exchange, string routingKey, IDictionary arguments) { ThrowIfDisposed(); @@ -562,18 +560,38 @@ public void QueueUnbind(string queue, string exchange, string routingKey, IDicti _connection.DeleteAutoDeleteExchange(exchange); } + public async ValueTask QueueUnbindAsync(string queue, string exchange, string routingKey, IDictionary arguments) + { + ThrowIfDisposed(); + _connection.DeleteRecordedBinding(new RecordedBinding(true, queue, exchange, routingKey, arguments)); + await _innerChannel.QueueUnbindAsync(queue, exchange, routingKey, arguments); + _connection.DeleteAutoDeleteExchange(exchange); + } + public void TxCommit() => InnerChannel.TxCommit(); + public ValueTask TxCommitAsync() + => InnerChannel.TxCommitAsync(); + public void TxRollback() => InnerChannel.TxRollback(); + public ValueTask TxRollbackAsync() + => InnerChannel.TxRollbackAsync(); + public void TxSelect() { InnerChannel.TxSelect(); _usesTransactions = true; } + public ValueTask TxSelectAsync() + { + _usesTransactions = true; + return InnerChannel.TxSelectAsync(); + } + public Task WaitForConfirmsAsync(CancellationToken token = default) => InnerChannel.WaitForConfirmsAsync(token); diff --git a/projects/RabbitMQ.Client/client/impl/AutorecoveringConnection.cs b/projects/RabbitMQ.Client/client/impl/AutorecoveringConnection.cs index 625ba1b2d4..82629be0e4 100644 --- a/projects/RabbitMQ.Client/client/impl/AutorecoveringConnection.cs +++ b/projects/RabbitMQ.Client/client/impl/AutorecoveringConnection.cs @@ -173,7 +173,10 @@ public RecoveryAwareChannel CreateNonRecoveringChannel() public override string ToString() => $"AutorecoveringConnection({InnerConnection.Id},{Endpoint},{GetHashCode()})"; - internal IFrameHandler FrameHandler => InnerConnection.FrameHandler; + internal void CloseFrameHandler() + { + InnerConnection.FrameHandler.Close(); + } ///API-side invocation of updating the secret. public void UpdateSecret(string newSecret, string reason) diff --git a/projects/RabbitMQ.Client/client/impl/ChannelBase.cs b/projects/RabbitMQ.Client/client/impl/ChannelBase.cs index 7367a95b10..4431cdcc6b 100644 --- a/projects/RabbitMQ.Client/client/impl/ChannelBase.cs +++ b/projects/RabbitMQ.Client/client/impl/ChannelBase.cs @@ -77,7 +77,6 @@ protected ChannelBase(ConnectionConfig config, ISession session) Action onException = (exception, context) => OnCallbackException(CallbackExceptionEventArgs.Build(exception, context)); _basicAcksWrapper = new EventingWrapper("OnBasicAck", onException); _basicNacksWrapper = new EventingWrapper("OnBasicNack", onException); - _basicRecoverOkWrapper = new EventingWrapper("OnBasicRecover", onException); _basicReturnWrapper = new EventingWrapper("OnBasicReturn", onException); _callbackExceptionWrapper = new EventingWrapper(string.Empty, (exception, context) => { }); _flowControlWrapper = new EventingWrapper("OnFlowControl", onException); @@ -105,13 +104,6 @@ public event EventHandler BasicNacks } private EventingWrapper _basicNacksWrapper; - public event EventHandler BasicRecoverOk - { - add => _basicRecoverOkWrapper.AddHandler(value); - remove => _basicRecoverOkWrapper.RemoveHandler(value); - } - private EventingWrapper _basicRecoverOkWrapper; - public event EventHandler BasicReturn { add => _basicReturnWrapper.AddHandler(value); @@ -184,7 +176,6 @@ protected void TakeOver(ChannelBase other) { _basicAcksWrapper.Takeover(other._basicAcksWrapper); _basicNacksWrapper.Takeover(other._basicNacksWrapper); - _basicRecoverOkWrapper.Takeover(other._basicRecoverOkWrapper); _basicReturnWrapper.Takeover(other._basicReturnWrapper); _callbackExceptionWrapper.Takeover(other._callbackExceptionWrapper); _flowControlWrapper.Takeover(other._flowControlWrapper); @@ -194,24 +185,68 @@ protected void TakeOver(ChannelBase other) public void Close(ushort replyCode, string replyText, bool abort) { - _ = CloseAsync(new ShutdownEventArgs(ShutdownInitiator.Application, replyCode, replyText), abort); - } - - private async Task CloseAsync(ShutdownEventArgs reason, bool abort) - { - // TODO LRB #1347 use async continuation + var reason = new ShutdownEventArgs(ShutdownInitiator.Application, replyCode, replyText); var k = new ShutdownContinuation(); ChannelShutdown += k.OnConnectionShutdown; try { ConsumerDispatcher.Quiesce(); + if (SetCloseReason(reason)) { _Private_ChannelClose(reason.ReplyCode, reason.ReplyText, 0, 0); } k.Wait(TimeSpan.FromMilliseconds(10000)); + ConsumerDispatcher.WaitForShutdownAsync().ConfigureAwait(false); + } + catch (AlreadyClosedException) + { + if (!abort) + { + throw; + } + } + catch (IOException) + { + if (!abort) + { + throw; + } + } + catch (Exception) + { + if (!abort) + { + throw; + } + } + finally + { + ChannelShutdown -= k.OnConnectionShutdown; + } + } + + public async ValueTask CloseAsync(ShutdownEventArgs reason, bool abort) + { + await _rpcSemaphore.WaitAsync().ConfigureAwait(false); + using var k = new ChannelCloseAsyncRpcContinuation(ContinuationTimeout); + try + { + ChannelShutdown += k.OnConnectionShutdown; + Enqueue(k); + ConsumerDispatcher.Quiesce(); + + if (SetCloseReason(reason)) + { + var method = new ChannelClose(reason.ReplyCode, reason.ReplyText, reason.ClassId, reason.MethodId); + await ModelSendAsync(method).ConfigureAwait(false); + } + + bool result = await k; + Debug.Assert(result); + await ConsumerDispatcher.WaitForShutdownAsync().ConfigureAwait(false); } catch (AlreadyClosedException) @@ -237,6 +272,7 @@ private async Task CloseAsync(ShutdownEventArgs reason, bool abort) } finally { + _rpcSemaphore.Release(); ChannelShutdown -= k.OnConnectionShutdown; } } @@ -248,11 +284,12 @@ internal async ValueTask ConnectionOpenAsync(string virtualHost) internal async ValueTask ConnectionSecureOkAsync(byte[] response) { - using var k = new ConnectionSecureOrTuneContinuation(ContinuationTimeout); await _rpcSemaphore.WaitAsync().ConfigureAwait(false); - Enqueue(k); try { + using var k = new ConnectionSecureOrTuneContinuation(ContinuationTimeout); + Enqueue(k); + try { _Private_ConnectionSecureOk(response); @@ -275,11 +312,12 @@ internal async ValueTask ConnectionSecureOkAsync(byte[] internal async ValueTask ConnectionStartOkAsync(IDictionary clientProperties, string mechanism, byte[] response, string locale) { - using var k = new ConnectionSecureOrTuneContinuation(ContinuationTimeout); await _rpcSemaphore.WaitAsync().ConfigureAwait(false); - Enqueue(k); try { + using var k = new ConnectionSecureOrTuneContinuation(ContinuationTimeout); + Enqueue(k); + try { // TODO LRB rabbitmq/rabbitmq-dotnet-client#1347 @@ -637,24 +675,32 @@ protected void HandleBasicDeliver(in IncomingCommand cmd) cmd.TakeoverBody()); } - protected void HandleBasicGetOk(in IncomingCommand cmd) + protected bool HandleBasicGetOk(in IncomingCommand cmd) { - var method = new BasicGetOk(cmd.MethodBytes.Span); - cmd.ReturnMethodBuffer(); - var header = new ReadOnlyBasicProperties(cmd.HeaderBytes.Span); - cmd.ReturnHeaderBuffer(); + if (_continuationQueue.TryPeek(out var k)) + { + var method = new BasicGetOk(cmd.MethodBytes.Span); + cmd.ReturnMethodBuffer(); + var header = new ReadOnlyBasicProperties(cmd.HeaderBytes.Span); + cmd.ReturnHeaderBuffer(); - var k = (BasicGetRpcContinuation)_continuationQueue.Next(); - k.m_result = new BasicGetResult( - AdjustDeliveryTag(method._deliveryTag), - method._redelivered, - method._exchange, - method._routingKey, - method._messageCount, - header, - cmd.Body, - cmd.TakeoverBody()); - k.HandleCommand(IncomingCommand.Empty); // release the continuation. + _continuationQueue.Next(); + k.m_result = new BasicGetResult( + AdjustDeliveryTag(method._deliveryTag), + method._redelivered, + method._exchange, + method._routingKey, + method._messageCount, + header, + cmd.Body, + cmd.TakeoverBody()); + k.HandleCommand(IncomingCommand.Empty); // release the continuation. + return true; + } + else + { + return false; + } } protected virtual ulong AdjustDeliveryTag(ulong deliveryTag) @@ -669,13 +715,6 @@ protected void HandleBasicGetEmpty() k.HandleCommand(IncomingCommand.Empty); // release the continuation. } - protected void HandleBasicRecoverOk() - { - var k = (SimpleBlockingRpcContinuation)_continuationQueue.Next(); - _basicRecoverOkWrapper.Invoke(this, EventArgs.Empty); - k.HandleCommand(IncomingCommand.Empty); - } - protected void HandleBasicReturn(in IncomingCommand cmd) { if (!_basicReturnWrapper.IsEmpty) @@ -718,9 +757,26 @@ protected void HandleChannelClose(in IncomingCommand cmd) } } - protected void HandleChannelCloseOk() + protected void HandleChannelCloseOk(in IncomingCommand cmd) { - FinishClose(); + try + { + /* + * Note: + * This call _must_ come before completing the async continuation + */ + FinishClose(); + + if (_continuationQueue.TryPeek(out var k)) + { + _continuationQueue.Next(); + k.HandleCommand(cmd); + } + } + finally + { + cmd.ReturnMethodBuffer(); + } } protected void HandleChannelFlow(in IncomingCommand cmd) @@ -836,8 +892,6 @@ protected bool HandleQueueDeclareOk(in IncomingCommand cmd) public abstract void _Private_BasicGet(string queue, bool autoAck); - public abstract void _Private_BasicRecover(bool requeue); - public abstract void _Private_ChannelClose(ushort replyCode, string replyText, ushort classId, ushort methodId); public abstract void _Private_ChannelCloseOk(); @@ -898,10 +952,10 @@ public void BasicCancel(string consumerTag) public async ValueTask BasicCancelAsync(string consumerTag) { - using var k = new BasicCancelAsyncRpcContinuation(ContinuationTimeout); await _rpcSemaphore.WaitAsync().ConfigureAwait(false); try { + using var k = new BasicCancelAsyncRpcContinuation(ContinuationTimeout); Enqueue(k); var method = new Client.Framing.Impl.BasicCancel(consumerTag, false); @@ -969,10 +1023,10 @@ public async ValueTask BasicConsumeAsync(string queue, bool autoAck, str } } - using var k = new BasicConsumeAsyncRpcContinuation(consumer, ConsumerDispatcher, ContinuationTimeout); await _rpcSemaphore.WaitAsync().ConfigureAwait(false); try { + using var k = new BasicConsumeAsyncRpcContinuation(consumer, ConsumerDispatcher, ContinuationTimeout); Enqueue(k); var method = new Client.Framing.Impl.BasicConsume(queue, consumerTag, noLocal, autoAck, exclusive, false, arguments); @@ -1005,8 +1059,29 @@ public BasicGetResult BasicGet(string queue, bool autoAck) return k.m_result; } + public async ValueTask BasicGetAsync(string queue, bool autoAck) + { + await _rpcSemaphore.WaitAsync().ConfigureAwait(false); + try + { + using var k = new BasicGetAsyncRpcContinuation(AdjustDeliveryTag, ContinuationTimeout); + Enqueue(k); + + var method = new BasicGet(queue, autoAck); + await ModelSendAsync(method).ConfigureAwait(false); + + return await k; + } + finally + { + _rpcSemaphore.Release(); + } + } + public abstract void BasicNack(ulong deliveryTag, bool multiple, bool requeue); + public abstract ValueTask BasicNackAsync(ulong deliveryTag, bool multiple, bool requeue); + public void BasicPublish(string exchange, string routingKey, in TProperties basicProperties, ReadOnlyMemory body, bool mandatory) where TProperties : IReadOnlyBasicProperties, IAmqpHeader { @@ -1086,10 +1161,10 @@ public void UpdateSecret(string newSecret, string reason) public async ValueTask BasicQosAsync(uint prefetchSize, ushort prefetchCount, bool global) { - using var k = new BasicQosAsyncRpcContinuation(ContinuationTimeout); await _rpcSemaphore.WaitAsync().ConfigureAwait(false); try { + using var k = new BasicQosAsyncRpcContinuation(ContinuationTimeout); Enqueue(k); var method = new BasicQos(prefetchSize, prefetchCount, global); @@ -1105,25 +1180,6 @@ public async ValueTask BasicQosAsync(uint prefetchSize, ushort prefetchCount, bo } } - public void BasicRecover(bool requeue) - { - var k = new SimpleBlockingRpcContinuation(); - - _rpcSemaphore.Wait(); - try - { - Enqueue(k); - _Private_BasicRecover(requeue); - k.GetReply(ContinuationTimeout); - } - finally - { - _rpcSemaphore.Release(); - } - } - - public abstract void BasicRecoverAsync(bool requeue); - public abstract void BasicReject(ulong deliveryTag, bool requeue); public abstract ValueTask BasicRejectAsync(ulong deliveryTag, bool requeue); @@ -1139,6 +1195,34 @@ public void ConfirmSelect() _Private_ConfirmSelect(false); } + public async ValueTask ConfirmSelectAsync() + { + await _rpcSemaphore.WaitAsync().ConfigureAwait(false); + try + { + if (NextPublishSeqNo == 0UL) + { + _confirmsTaskCompletionSources = new List>(); + NextPublishSeqNo = 1; + } + + using var k = new ConfirmSelectAsyncRpcContinuation(ContinuationTimeout); + Enqueue(k); + + var method = new ConfirmSelect(false); + await ModelSendAsync(method).ConfigureAwait(false); + + bool result = await k; + Debug.Assert(result); + + return; + } + finally + { + _rpcSemaphore.Release(); + } + } + public void ExchangeBind(string destination, string source, string routingKey, IDictionary arguments) { _Private_ExchangeBind(destination, source, routingKey, false, arguments); @@ -1146,10 +1230,10 @@ public void ExchangeBind(string destination, string source, string routingKey, I public async ValueTask ExchangeBindAsync(string destination, string source, string routingKey, IDictionary arguments) { - using var k = new ExchangeBindAsyncRpcContinuation(ContinuationTimeout); await _rpcSemaphore.WaitAsync().ConfigureAwait(false); try { + using var k = new ExchangeBindAsyncRpcContinuation(ContinuationTimeout); Enqueue(k); var method = new ExchangeBind(destination, source, routingKey, false, arguments); @@ -1177,10 +1261,10 @@ public void ExchangeDeclare(string exchange, string type, bool durable, bool aut public async ValueTask ExchangeDeclareAsync(string exchange, string type, bool passive, bool durable, bool autoDelete, IDictionary arguments) { - using var k = new ExchangeDeclareAsyncRpcContinuation(ContinuationTimeout); await _rpcSemaphore.WaitAsync().ConfigureAwait(false); try { + using var k = new ExchangeDeclareAsyncRpcContinuation(ContinuationTimeout); Enqueue(k); var method = new ExchangeDeclare(exchange, type, passive, durable, autoDelete, false, false, arguments); @@ -1244,10 +1328,10 @@ public void ExchangeUnbind(string destination, string source, string routingKey, public async ValueTask ExchangeUnbindAsync(string destination, string source, string routingKey, IDictionary arguments) { - using var k = new ExchangeUnbindAsyncRpcContinuation(ContinuationTimeout); await _rpcSemaphore.WaitAsync().ConfigureAwait(false); try { + using var k = new ExchangeUnbindAsyncRpcContinuation(ContinuationTimeout); Enqueue(k); var method = new ExchangeUnbind(destination, source, routingKey, false, arguments); @@ -1285,10 +1369,10 @@ public QueueDeclareOk QueueDeclare(string queue, bool durable, bool exclusive, b public async ValueTask QueueDeclareAsync(string queue, bool passive, bool durable, bool exclusive, bool autoDelete, IDictionary arguments) { - using var k = new QueueDeclareAsyncRpcContinuation(ContinuationTimeout); await _rpcSemaphore.WaitAsync().ConfigureAwait(false); try { + using var k = new QueueDeclareAsyncRpcContinuation(ContinuationTimeout); Enqueue(k); var method = new QueueDeclare(queue, passive, durable, exclusive, autoDelete, false, arguments); @@ -1309,10 +1393,10 @@ public async ValueTask QueueDeclareAsync(string queue, bool pass public async ValueTask QueueBindAsync(string queue, string exchange, string routingKey, IDictionary arguments) { - using var k = new QueueBindAsyncRpcContinuation(ContinuationTimeout); await _rpcSemaphore.WaitAsync().ConfigureAwait(false); try { + using var k = new QueueBindAsyncRpcContinuation(ContinuationTimeout); Enqueue(k); var method = new QueueBind(queue, exchange, routingKey, false, arguments); @@ -1357,10 +1441,10 @@ public uint QueueDelete(string queue, bool ifUnused, bool ifEmpty) public async ValueTask QueueDeleteAsync(string queue, bool ifUnused, bool ifEmpty) { - var k = new QueueDeleteAsyncRpcContinuation(ContinuationTimeout); await _rpcSemaphore.WaitAsync().ConfigureAwait(false); try { + var k = new QueueDeleteAsyncRpcContinuation(ContinuationTimeout); Enqueue(k); var method = new QueueDelete(queue, ifUnused, ifEmpty, false); @@ -1384,14 +1468,117 @@ public uint QueuePurge(string queue) return _Private_QueuePurge(queue, false); } + public async ValueTask QueuePurgeAsync(string queue) + { + await _rpcSemaphore.WaitAsync().ConfigureAwait(false); + try + { + var k = new QueuePurgeAsyncRpcContinuation(ContinuationTimeout); + Enqueue(k); + + var method = new QueuePurge(queue, false); + await ModelSendAsync(method).ConfigureAwait(false); + + return await k; + } + finally + { + _rpcSemaphore.Release(); + } + } + public abstract void QueueUnbind(string queue, string exchange, string routingKey, IDictionary arguments); + public async ValueTask QueueUnbindAsync(string queue, string exchange, string routingKey, IDictionary arguments) + { + await _rpcSemaphore.WaitAsync().ConfigureAwait(false); + try + { + using var k = new QueueUnbindAsyncRpcContinuation(ContinuationTimeout); + Enqueue(k); + + var method = new QueueUnbind(queue, exchange, routingKey, arguments); + await ModelSendAsync(method).ConfigureAwait(false); + + bool result = await k; + Debug.Assert(result); + return; + } + finally + { + _rpcSemaphore.Release(); + } + } + public abstract void TxCommit(); + public async ValueTask TxCommitAsync() + { + await _rpcSemaphore.WaitAsync().ConfigureAwait(false); + try + { + using var k = new TxCommitAsyncRpcContinuation(ContinuationTimeout); + Enqueue(k); + + var method = new TxCommit(); + await ModelSendAsync(method).ConfigureAwait(false); + + bool result = await k; + Debug.Assert(result); + return; + } + finally + { + _rpcSemaphore.Release(); + } + } + public abstract void TxRollback(); + public async ValueTask TxRollbackAsync() + { + await _rpcSemaphore.WaitAsync().ConfigureAwait(false); + try + { + using var k = new TxRollbackAsyncRpcContinuation(ContinuationTimeout); + Enqueue(k); + + var method = new TxRollback(); + await ModelSendAsync(method).ConfigureAwait(false); + + bool result = await k; + Debug.Assert(result); + return; + } + finally + { + _rpcSemaphore.Release(); + } + } + public abstract void TxSelect(); + public async ValueTask TxSelectAsync() + { + await _rpcSemaphore.WaitAsync().ConfigureAwait(false); + try + { + using var k = new TxSelectAsyncRpcContinuation(ContinuationTimeout); + Enqueue(k); + + var method = new TxSelect(); + await ModelSendAsync(method).ConfigureAwait(false); + + bool result = await k; + Debug.Assert(result); + return; + } + finally + { + _rpcSemaphore.Release(); + } + } + private List> _confirmsTaskCompletionSources; public Task WaitForConfirmsAsync(CancellationToken token = default) @@ -1465,13 +1652,16 @@ await CloseAsync( new IOException("nack received")), false).ConfigureAwait(false); } - catch (TaskCanceledException exception) + catch (TaskCanceledException) { + const string msg = "timed out waiting for acks"; + var ex = new IOException(msg); await CloseAsync(new ShutdownEventArgs(ShutdownInitiator.Library, Constants.ReplySuccess, - "Timed out waiting for acks", - exception), + msg, + ex), false).ConfigureAwait(false); + throw ex; } } diff --git a/projects/RabbitMQ.Client/client/impl/Connection.Receive.cs b/projects/RabbitMQ.Client/client/impl/Connection.Receive.cs index 52bb68d2ec..bbfeda1be9 100644 --- a/projects/RabbitMQ.Client/client/impl/Connection.Receive.cs +++ b/projects/RabbitMQ.Client/client/impl/Connection.Receive.cs @@ -144,6 +144,8 @@ private void HandleMainLoopException(ShutdownEventArgs reason) { if (!SetCloseReason(reason)) { + // TODO LRB + // reason.Cause could be an Exception, should we use that? LogCloseError("Unexpected Main Loop Exception while closing: " + reason, new Exception(reason.ToString())); return; } diff --git a/projects/RabbitMQ.Client/client/impl/RecoveryAwareChannel.cs b/projects/RabbitMQ.Client/client/impl/RecoveryAwareChannel.cs index 577726a56a..e9972d9743 100644 --- a/projects/RabbitMQ.Client/client/impl/RecoveryAwareChannel.cs +++ b/projects/RabbitMQ.Client/client/impl/RecoveryAwareChannel.cs @@ -81,11 +81,7 @@ public override ValueTask BasicAckAsync(ulong deliveryTag, bool multiple) } else { -#if NET6_0_OR_GREATER - return ValueTask.CompletedTask; -#else - return new ValueTask(Task.CompletedTask); -#endif + return default; } } @@ -98,6 +94,19 @@ public override void BasicNack(ulong deliveryTag, bool multiple, bool requeue) } } + public override ValueTask BasicNackAsync(ulong deliveryTag, bool multiple, bool requeue) + { + ulong realTag = deliveryTag - ActiveDeliveryTagOffset; + if (realTag > 0 && realTag <= deliveryTag) + { + return base.BasicNackAsync(realTag, multiple, requeue); + } + else + { + return default; + } + } + public override void BasicReject(ulong deliveryTag, bool requeue) { ulong realTag = deliveryTag - ActiveDeliveryTagOffset; @@ -116,11 +125,7 @@ public override ValueTask BasicRejectAsync(ulong deliveryTag, bool requeue) } else { -#if NET6_0_OR_GREATER - return ValueTask.CompletedTask; -#else - return new ValueTask(Task.CompletedTask); -#endif + return default; } } } diff --git a/projects/RabbitMQ.Client/client/impl/SocketFrameHandler.cs b/projects/RabbitMQ.Client/client/impl/SocketFrameHandler.cs index c09bdd1b56..c02693152b 100644 --- a/projects/RabbitMQ.Client/client/impl/SocketFrameHandler.cs +++ b/projects/RabbitMQ.Client/client/impl/SocketFrameHandler.cs @@ -282,11 +282,7 @@ public ValueTask WriteAsync(ReadOnlyMemory memory) { if (_closed) { -#if NET6_0_OR_GREATER - return ValueTask.CompletedTask; -#else - return new ValueTask(Task.CompletedTask); -#endif + return default; } return _channelWriter.WriteAsync(memory); diff --git a/projects/TestApplications/CreateChannel/CreateChannel.csproj b/projects/Test/Applications/CreateChannel/CreateChannel.csproj similarity index 83% rename from projects/TestApplications/CreateChannel/CreateChannel.csproj rename to projects/Test/Applications/CreateChannel/CreateChannel.csproj index 9fe7c226d7..9316c348d1 100644 --- a/projects/TestApplications/CreateChannel/CreateChannel.csproj +++ b/projects/Test/Applications/CreateChannel/CreateChannel.csproj @@ -13,7 +13,7 @@ - + diff --git a/projects/TestApplications/CreateChannel/Program.cs b/projects/Test/Applications/CreateChannel/Program.cs similarity index 100% rename from projects/TestApplications/CreateChannel/Program.cs rename to projects/Test/Applications/CreateChannel/Program.cs diff --git a/projects/TestApplications/MassPublish/MassPublish.csproj b/projects/Test/Applications/MassPublish/MassPublish.csproj similarity index 83% rename from projects/TestApplications/MassPublish/MassPublish.csproj rename to projects/Test/Applications/MassPublish/MassPublish.csproj index 9fe7c226d7..9316c348d1 100644 --- a/projects/TestApplications/MassPublish/MassPublish.csproj +++ b/projects/Test/Applications/MassPublish/MassPublish.csproj @@ -13,7 +13,7 @@ - + diff --git a/projects/TestApplications/MassPublish/Program.cs b/projects/Test/Applications/MassPublish/Program.cs similarity index 100% rename from projects/TestApplications/MassPublish/Program.cs rename to projects/Test/Applications/MassPublish/Program.cs diff --git a/projects/Test/Common/Common.csproj b/projects/Test/Common/Common.csproj new file mode 100644 index 0000000000..215713acfa --- /dev/null +++ b/projects/Test/Common/Common.csproj @@ -0,0 +1,38 @@ + + + + net6.0;net472 + + + + net6.0 + + + + ../../rabbit.snk + true + latest + 7.0 + false + + + + + + <_Parameter1>Integration, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d20ec856aeeb8c3153a77faa2d80e6e43b5db93224a20cc7ae384f65f142e89730e2ff0fcc5d578bbe96fa98a7196c77329efdee4579b3814c0789e5a39b51df6edd75b602a33ceabdfcf19a3feb832f31d8254168cd7ba5700dfbca301fbf8db614ba41ba18474de0a5f4c2d51c995bc3636c641c8cbe76f45717bfcb943b5 + + + <_Parameter1>SequentialIntegration, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d20ec856aeeb8c3153a77faa2d80e6e43b5db93224a20cc7ae384f65f142e89730e2ff0fcc5d578bbe96fa98a7196c77329efdee4579b3814c0789e5a39b51df6edd75b602a33ceabdfcf19a3feb832f31d8254168cd7ba5700dfbca301fbf8db614ba41ba18474de0a5f4c2d51c995bc3636c641c8cbe76f45717bfcb943b5 + + + + + + + + + + + + + diff --git a/projects/Test/Common/IntegrationFixture.cs b/projects/Test/Common/IntegrationFixture.cs new file mode 100644 index 0000000000..19ad04909d --- /dev/null +++ b/projects/Test/Common/IntegrationFixture.cs @@ -0,0 +1,282 @@ +// This source code is dual-licensed under the Apache License, version +// 2.0, and the Mozilla Public License, version 2.0. +// +// The APL v2.0: +// +//--------------------------------------------------------------------------- +// Copyright (c) 2007-2020 VMware, Inc. +// +// 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 +// +// https://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. +//--------------------------------------------------------------------------- +// +// The MPL v2.0: +// +//--------------------------------------------------------------------------- +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2007-2020 VMware, Inc. All rights reserved. +//--------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Reflection; +using System.Text; +using System.Threading; +using RabbitMQ.Client; +using RabbitMQ.Client.Framing.Impl; +using Xunit; +using Xunit.Abstractions; + +namespace Test +{ + public class IntegrationFixture : IDisposable + { + private static int _connectionIdx = 0; + + protected readonly RabbitMQCtl _rabbitMQCtl; + + protected ConnectionFactory _connFactory; + protected IConnection _conn; + protected IChannel _channel; + + protected static readonly Encoding _encoding = new UTF8Encoding(); + protected static readonly int _processorCount = Environment.ProcessorCount; + + protected readonly ITestOutputHelper _output; + protected readonly string _testDisplayName; + + public static readonly TimeSpan WaitSpan; + public static readonly TimeSpan LongWaitSpan; + public static readonly TimeSpan RecoveryInterval = TimeSpan.FromSeconds(2); + public static readonly Random S_Random; + + static IntegrationFixture() + { + S_Random = new Random(); + + int threadCount = _processorCount * 16; + ThreadPool.SetMinThreads(threadCount, threadCount); + + if (IsRunningInCI()) + { + WaitSpan = TimeSpan.FromSeconds(30); + LongWaitSpan = TimeSpan.FromSeconds(60); + } + else + { + WaitSpan = TimeSpan.FromSeconds(15); + LongWaitSpan = TimeSpan.FromSeconds(30); + } + } + + public IntegrationFixture(ITestOutputHelper output) + { + _output = output; + _rabbitMQCtl = new RabbitMQCtl(_output); + + var type = _output.GetType(); + var testMember = type.GetField("test", BindingFlags.Instance | BindingFlags.NonPublic); + var test = (ITest)testMember.GetValue(output); + _testDisplayName = test.DisplayName; + + SetUp(); + } + + protected virtual void SetUp() + { + if (_connFactory == null) + { + _connFactory = CreateConnectionFactory(); + } + + if (_conn == null) + { + _conn = _connFactory.CreateConnection(); + _channel = _conn.CreateChannel(); + } + } + + public virtual void Dispose() + { + if (_channel != null) + { + _channel.Dispose(); + } + + if (_conn != null) + { + _conn.Dispose(); + } + + TearDown(); + } + + protected virtual void TearDown() + { + } + + protected static byte[] GetRandomBody(ushort size) + { + var body = new byte[size]; +#if NET6_0_OR_GREATER + Random.Shared.NextBytes(body); +#else + S_Random.NextBytes(body); +#endif + return body; + } + + internal AutorecoveringConnection CreateAutorecoveringConnection(IList hostnames) + { + return CreateAutorecoveringConnection(RecoveryInterval, hostnames); + } + + internal AutorecoveringConnection CreateAutorecoveringConnection(TimeSpan interval, IList hostnames) + { + var cf = CreateConnectionFactory(); + cf.AutomaticRecoveryEnabled = true; + // tests that use this helper will likely list unreachable hosts; + // make sure we time out quickly on those + cf.RequestedConnectionTimeout = TimeSpan.FromSeconds(1); + cf.NetworkRecoveryInterval = interval; + return (AutorecoveringConnection)cf.CreateConnection(hostnames); + } + + protected void WithTemporaryChannel(Action action) + { + IChannel channel = _conn.CreateChannel(); + + try + { + action(channel); + } + finally + { + channel.Abort(); + } + } + + protected string GenerateExchangeName() + { + return $"{_testDisplayName}-exchange-{Guid.NewGuid()}"; + } + + protected string GenerateQueueName() + { + return $"{_testDisplayName}-queue-{Guid.NewGuid()}"; + } + + protected void WithTemporaryNonExclusiveQueue(Action action) + { + WithTemporaryNonExclusiveQueue(_channel, action); + } + + protected void WithTemporaryNonExclusiveQueue(IChannel channel, Action action) + { + WithTemporaryNonExclusiveQueue(channel, action, GenerateQueueName()); + } + + protected void WithTemporaryNonExclusiveQueue(IChannel channel, Action action, string queue) + { + try + { + channel.QueueDeclare(queue, false, false, false, null); + action(channel, queue); + } + finally + { + WithTemporaryChannel(tm => tm.QueueDelete(queue)); + } + } + + protected void AssertMessageCount(string q, uint count) + { + WithTemporaryChannel((m) => + { + RabbitMQ.Client.QueueDeclareOk ok = m.QueueDeclarePassive(q); + Assert.Equal(count, ok.MessageCount); + }); + } + + protected void AssertShutdownError(ShutdownEventArgs args, int code) + { + Assert.Equal(args.ReplyCode, code); + } + + protected void AssertPreconditionFailed(ShutdownEventArgs args) + { + AssertShutdownError(args, Constants.PreconditionFailed); + } + + protected void WaitOn(object o) + { + lock (o) + { + Monitor.Wait(o, TimingFixture.TestTimeout); + } + } + + protected void Wait(ManualResetEventSlim latch, string desc) + { + Assert.True(latch.Wait(WaitSpan), + $"waiting {WaitSpan.TotalSeconds} seconds on a latch for '{desc}' timed out"); + } + + protected void Wait(ManualResetEventSlim latch, TimeSpan timeSpan, string desc) + { + Assert.True(latch.Wait(timeSpan), + $"waiting {timeSpan.TotalSeconds} seconds on a latch for '{desc}' timed out"); + } + + protected ConnectionFactory CreateConnectionFactory() + { + string now = DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture); + return new ConnectionFactory + { + ClientProvidedName = $"{_testDisplayName}:{now}:{GetConnectionIdx()}", + ContinuationTimeout = WaitSpan, + HandshakeContinuationTimeout = WaitSpan, + }; + } + + private static int GetConnectionIdx() + { + return Interlocked.Increment(ref _connectionIdx); + } + + private static bool IsRunningInCI() + { + bool ci = false; + + if (bool.TryParse(Environment.GetEnvironmentVariable("CI"), out ci)) + { + if (ci == true) + { + return ci; + } + } + else if (bool.TryParse(Environment.GetEnvironmentVariable("GITHUB_ACTIONS"), out ci)) + { + if (ci == true) + { + return ci; + } + } + + return ci; + } + } +} diff --git a/projects/Unit/RabbitMQCtl.cs b/projects/Test/Common/RabbitMQCtl.cs similarity index 67% rename from projects/Unit/RabbitMQCtl.cs rename to projects/Test/Common/RabbitMQCtl.cs index 7a12e97f51..8faf25ba39 100644 --- a/projects/Unit/RabbitMQCtl.cs +++ b/projects/Test/Common/RabbitMQCtl.cs @@ -29,72 +29,56 @@ // Copyright (c) 2007-2020 VMware, Inc. All rights reserved. //--------------------------------------------------------------------------- -#pragma warning disable 2002 - using System; using System.Diagnostics; using System.IO; using System.Text; using System.Text.RegularExpressions; -using System.Threading; +using RabbitMQ.Client; +using Xunit.Abstractions; -namespace RabbitMQ.Client.Unit +namespace Test { -#nullable enable - public static class RabbitMQCtl + public class RabbitMQCtl { private static readonly char[] newLine = new char[] { '\n' }; private static readonly Func s_invokeRabbitMqCtl = GetRabbitMqCtlInvokeAction(); + private static readonly Regex s_getConnectionProperties = + new Regex(@"^(?<[^>]*>)\s\[.*""connection_name"",""(?[^""]*)"".*\]$", RegexOptions.Multiline | RegexOptions.Compiled); - private static Func GetRabbitMqCtlInvokeAction() - { - string precomputedArguments; - string? envVariable = Environment.GetEnvironmentVariable("RABBITMQ_RABBITMQCTL_PATH"); + private readonly ITestOutputHelper _output; - if (!string.IsNullOrWhiteSpace(envVariable)) - { - const string DockerPrefix = "DOCKER:"; - if (envVariable.StartsWith(DockerPrefix)) - { - // Call docker - precomputedArguments = $"exec {envVariable.Substring(DockerPrefix.Length)} rabbitmqctl "; - return args => CreateProcess("docker", precomputedArguments + args); - } - - // call the path from the env var - return args => CreateProcess(envVariable, args); - } + public RabbitMQCtl(ITestOutputHelper output) + { + _output = output; + } - // Try default - string umbrellaRabbitmqctlPath; - string providedRabbitmqctlPath; + public void CloseConnection(IConnection conn) + { + CloseConnection(GetConnectionPid(conn.ClientProvidedName)); + } - if (IsRunningOnMonoOrDotNetCore()) - { - umbrellaRabbitmqctlPath = "../../../../../../rabbit/scripts/rabbitmqctl"; - providedRabbitmqctlPath = "rabbitmqctl"; - } - else - { - umbrellaRabbitmqctlPath = @"..\..\..\..\..\..\rabbit\scripts\rabbitmqctl.bat"; - providedRabbitmqctlPath = "rabbitmqctl.bat"; - } + public void AddUser(string username, string password) + { + ExecRabbitMQCtl($"add_user {username} {password}"); + } - string path = File.Exists(umbrellaRabbitmqctlPath) ? umbrellaRabbitmqctlPath : providedRabbitmqctlPath; + public void ChangePassword(string username, string password) + { + ExecRabbitMQCtl($"change_password {username} {password}"); + } - if (IsRunningOnMonoOrDotNetCore()) - { - return args => CreateProcess(path, args); - } + public void SetPermissions(string username, string conf, string write, string read) + { + ExecRabbitMQCtl($"set_permissions {username} \"{conf}\" \"{write}\" \"${read}\" "); + } - precomputedArguments = $"/c \"\"{path}\" "; - return args => CreateProcess("cmd.exe", precomputedArguments + args); + public void DeleteUser(string username) + { + ExecRabbitMQCtl($"delete_user {username}"); } - // - // Shelling Out - // - private static string ExecRabbitMQCtl(string args) + public string ExecRabbitMQCtl(string args) { try { @@ -118,66 +102,61 @@ private static string ExecRabbitMQCtl(string args) } } - private static Process CreateProcess(string cmd, string arguments, string? workDirectory = null) + private void ReportExecFailure(string cmd, string args, string msg) { - return new Process - { - StartInfo = - { - CreateNoWindow = true, - UseShellExecute = false, - RedirectStandardError = true, - RedirectStandardOutput = true, - FileName = cmd, - Arguments = arguments, - WorkingDirectory = workDirectory - } - }; + _output.WriteLine($"Failure while running {cmd} {args}:\n{msg}"); } - private static void ReportExecFailure(string cmd, string args, string msg) + private static Func GetRabbitMqCtlInvokeAction() { - Console.WriteLine($"Failure while running {cmd} {args}:\n{msg}"); - } + string precomputedArguments; + string envVariable = Environment.GetEnvironmentVariable("RABBITMQ_RABBITMQCTL_PATH"); - private static bool IsRunningOnMonoOrDotNetCore() - { -#if NETCOREAPP - return true; -#else - return Type.GetType("Mono.Runtime") != null; -#endif - } + if (!string.IsNullOrWhiteSpace(envVariable)) + { + const string DockerPrefix = "DOCKER:"; + if (envVariable.StartsWith(DockerPrefix)) + { + // Call docker + precomputedArguments = $"exec {envVariable.Substring(DockerPrefix.Length)} rabbitmqctl "; + return args => CreateProcess("docker", precomputedArguments + args); + } + else + { + // call the path from the env var + return args => CreateProcess(envVariable, args); + } + } - // - // Flow Control - // - public static void Block(IConnection conn, Encoding encoding) - { - ExecRabbitMQCtl("set_vm_memory_high_watermark 0.000000001"); - // give rabbitmqctl some time to do its job - Thread.Sleep(1200); - Publish(conn, encoding); - } + // Try default + string umbrellaRabbitmqctlPath; + string providedRabbitmqctlPath; - public static void Publish(IConnection conn, Encoding encoding) - { - IChannel ch = conn.CreateChannel(); - ch.BasicPublish("amq.fanout", "", encoding.GetBytes("message")); - } + if (IsRunningOnMonoOrDotNetCore()) + { + umbrellaRabbitmqctlPath = "../../../../../../rabbit/scripts/rabbitmqctl"; + providedRabbitmqctlPath = "rabbitmqctl"; + } + else + { + umbrellaRabbitmqctlPath = @"..\..\..\..\..\..\rabbit\scripts\rabbitmqctl.bat"; + providedRabbitmqctlPath = "rabbitmqctl.bat"; + } - public static void Unblock() - { - ExecRabbitMQCtl("set_vm_memory_high_watermark 0.4"); - } + string path = File.Exists(umbrellaRabbitmqctlPath) ? umbrellaRabbitmqctlPath : providedRabbitmqctlPath; - public static void CloseConnection(IConnection conn) - { - CloseConnection(GetConnectionPid(conn.ClientProvidedName)); + if (IsRunningOnMonoOrDotNetCore()) + { + return args => CreateProcess(path, args); + } + else + { + precomputedArguments = $"/c \"\"{path}\" "; + return args => CreateProcess("cmd.exe", precomputedArguments + args); + } } - private static readonly Regex s_getConnectionProperties = new Regex(@"^(?<[^>]*>)\s\[.*""connection_name"",""(?[^""]*)"".*\]$", RegexOptions.Multiline | RegexOptions.Compiled); - private static string GetConnectionPid(string connectionName) + private string GetConnectionPid(string connectionName) { string stdout = ExecRabbitMQCtl("list_connections --silent pid client_properties"); @@ -195,65 +174,41 @@ private static string GetConnectionPid(string connectionName) throw new Exception($"No connection found with name: {connectionName}"); } - private static void CloseConnection(string pid) + private void CloseConnection(string pid) { ExecRabbitMQCtl($"close_connection \"{pid}\" \"Closed via rabbitmqctl\""); } - public static void CloseAllConnections() - { - foreach (var pid in EnumerateConnectionsPid()) - { - CloseConnection(pid); - } - } - - private static string[] EnumerateConnectionsPid() - { - string rabbitmqCtlResult = ExecRabbitMQCtl("list_connections --silent pid"); - return rabbitmqCtlResult.Split(newLine, StringSplitOptions.RemoveEmptyEntries); - } - - public static void RestartRabbitMQ() - { - StopRabbitMQ(); - Thread.Sleep(500); - StartRabbitMQ(); - AwaitRabbitMQ(); - } - - public static void StopRabbitMQ() - { - ExecRabbitMQCtl("stop_app"); - } - - public static void StartRabbitMQ() - { - ExecRabbitMQCtl("start_app"); - } - - public static void AwaitRabbitMQ() - { - ExecRabbitMQCtl("await_startup"); - } - - public static void AddUser(string username, string password) - { - ExecRabbitMQCtl($"add_user {username} {password}"); - } - public static void ChangePassword(string username, string password) + private static bool IsRunningOnMonoOrDotNetCore() { - ExecRabbitMQCtl($"change_password {username} {password}"); +#if NETCOREAPP + return true; +#else + return Type.GetType("Mono.Runtime") != null; +#endif } - public static void SetPermissions(string username, string conf, string write, string read) + private static void Publish(IConnection conn, Encoding encoding) { - ExecRabbitMQCtl($"set_permissions {username} \"{conf}\" \"{write}\" \"${read}\" "); + IChannel ch = conn.CreateChannel(); + ch.BasicPublish("amq.fanout", "", encoding.GetBytes("message")); } - public static void DeleteUser(string username) + private static Process CreateProcess(string cmd, string arguments, string workDirectory = null) { - ExecRabbitMQCtl($"delete_user {username}"); + return new Process + { + StartInfo = + { + CreateNoWindow = true, + UseShellExecute = false, + RedirectStandardError = true, + RedirectStandardOutput = true, + FileName = cmd, + Arguments = arguments, + WorkingDirectory = workDirectory + } + }; } } } diff --git a/projects/RabbitMQ.Client/client/framing/BasicRecoverAsync.cs b/projects/Test/Common/TimingFixture.cs similarity index 67% rename from projects/RabbitMQ.Client/client/framing/BasicRecoverAsync.cs rename to projects/Test/Common/TimingFixture.cs index e2c76097ea..23ce3a31ce 100644 --- a/projects/RabbitMQ.Client/client/framing/BasicRecoverAsync.cs +++ b/projects/Test/Common/TimingFixture.cs @@ -30,30 +30,17 @@ //--------------------------------------------------------------------------- using System; -using RabbitMQ.Client.client.framing; -using RabbitMQ.Client.Impl; -namespace RabbitMQ.Client.Framing.Impl +namespace Test { - internal readonly struct BasicRecoverAsync : IOutgoingAmqpMethod + public static class TimingFixture { - public readonly bool _requeue; - - public BasicRecoverAsync(bool Requeue) - { - _requeue = Requeue; - } - - public ProtocolCommandId ProtocolCommandId => ProtocolCommandId.BasicRecoverAsync; - - public int WriteTo(Span span) - { - return WireFormatting.WriteBits(ref span.GetStart(), _requeue); - } - - public int GetRequiredBufferSize() - { - return 1; // bytes for bit fields - } + public static readonly TimeSpan TimingInterval = TimeSpan.FromMilliseconds(300); + public static readonly TimeSpan TimingInterval_2X = TimeSpan.FromMilliseconds(600); + public static readonly TimeSpan TimingInterval_4X = TimeSpan.FromMilliseconds(1200); + public static readonly TimeSpan TimingInterval_8X = TimeSpan.FromMilliseconds(2400); + public static readonly TimeSpan TimingInterval_16X = TimeSpan.FromMilliseconds(4800); + public static readonly TimeSpan SafetyMargin = TimeSpan.FromMilliseconds(150); + public static readonly TimeSpan TestTimeout = TimeSpan.FromSeconds(5); } } diff --git a/projects/Test/Integration/Integration.csproj b/projects/Test/Integration/Integration.csproj new file mode 100644 index 0000000000..f775877b1e --- /dev/null +++ b/projects/Test/Integration/Integration.csproj @@ -0,0 +1,57 @@ + + + + net6.0;net472 + + + + net6.0 + + + + ../../rabbit.snk + true + latest + 7.0 + true + + + + + + + + + + + <_Parameter1>Xunit.CollectionBehavior.CollectionPerAssembly + <_Parameter1_IsLiteral>true + <_Parameter1_TypeName>Xunit.CollectionBehavior.CollectionPerAssembly + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + diff --git a/projects/Unit/SslEnv.cs b/projects/Test/Integration/SslEnv.cs similarity index 84% rename from projects/Unit/SslEnv.cs rename to projects/Test/Integration/SslEnv.cs index bc286a9d3d..583d90eaa3 100644 --- a/projects/Unit/SslEnv.cs +++ b/projects/Test/Integration/SslEnv.cs @@ -32,7 +32,7 @@ using System; using System.IO; -namespace RabbitMQ.Client.Unit +namespace Test.Integration { public class SslEnv { @@ -48,21 +48,24 @@ public SslEnv() _sslDir = Environment.GetEnvironmentVariable("SSL_CERTS_DIR"); _certPassphrase = Environment.GetEnvironmentVariable("PASSWORD"); - Boolean.TryParse(Environment.GetEnvironmentVariable("GITHUB_ACTIONS"), out _isGithubActions); - _isSslConfigured = Directory.Exists(_sslDir) && (false == String.IsNullOrEmpty(_certPassphrase)); - if (_isGithubActions) - { - _hostname = "localhost"; - } - else + if (_isSslConfigured) { - _hostname = System.Net.Dns.GetHostName(); - } + Boolean.TryParse(Environment.GetEnvironmentVariable("GITHUB_ACTIONS"), out _isGithubActions); + + if (_isGithubActions) + { + _hostname = "localhost"; + } + else + { + _hostname = System.Net.Dns.GetHostName(); + } - _certPath = Path.Combine(_sslDir, $"client_{_hostname}.p12"); + _certPath = Path.Combine(_sslDir, $"client_{_hostname}.p12"); + } } public string CertPath diff --git a/projects/Test/Integration/TestAsyncConsumer.cs b/projects/Test/Integration/TestAsyncConsumer.cs new file mode 100644 index 0000000000..6c6b69a69c --- /dev/null +++ b/projects/Test/Integration/TestAsyncConsumer.cs @@ -0,0 +1,316 @@ +// This source code is dual-licensed under the Apache License, version +// 2.0, and the Mozilla Public License, version 2.0. +// +// The APL v2.0: +// +//--------------------------------------------------------------------------- +// Copyright (c) 2007-2020 VMware, Inc. +// +// 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 +// +// https://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. +//--------------------------------------------------------------------------- +// +// The MPL v2.0: +// +//--------------------------------------------------------------------------- +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2007-2020 VMware, Inc. All rights reserved. +//--------------------------------------------------------------------------- + +using System; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using RabbitMQ.Client; +using RabbitMQ.Client.Events; +using Xunit; +using Xunit.Abstractions; + +namespace Test.Integration +{ + public class TestAsyncConsumer : IntegrationFixture + { + public TestAsyncConsumer(ITestOutputHelper output) : base(output) + { + } + + protected override void SetUp() + { + _connFactory = CreateConnectionFactory(); + _connFactory.DispatchConsumersAsync = true; + _connFactory.ConsumerDispatchConcurrency = 2; + + _conn = _connFactory.CreateConnection(); + _channel = _conn.CreateChannel(); + } + + [Fact] + public void TestBasicRoundtrip() + { + QueueDeclareOk q = _channel.QueueDeclare(); + byte[] body = _encoding.GetBytes("async-hi"); + _channel.BasicPublish("", q.QueueName, body); + var consumer = new AsyncEventingBasicConsumer(_channel); + var are = new AutoResetEvent(false); + consumer.Received += async (o, a) => + { + are.Set(); + await Task.Yield(); + }; + string tag = _channel.BasicConsume(q.QueueName, true, consumer); + // ensure we get a delivery + bool waitRes = are.WaitOne(2000); + Assert.True(waitRes); + // unsubscribe and ensure no further deliveries + _channel.BasicCancel(tag); + _channel.BasicPublish("", q.QueueName, body); + bool waitResFalse = are.WaitOne(2000); + Assert.False(waitResFalse); + } + + [Fact] + public async Task TestBasicRoundtripConcurrent() + { + QueueDeclareOk q = _channel.QueueDeclare(); + string publish1 = get_unique_string(1024); + byte[] body = _encoding.GetBytes(publish1); + _channel.BasicPublish("", q.QueueName, body); + + string publish2 = get_unique_string(1024); + body = _encoding.GetBytes(publish2); + _channel.BasicPublish("", q.QueueName, body); + + var consumer = new AsyncEventingBasicConsumer(_channel); + + var publish1SyncSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var publish2SyncSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var maximumWaitTime = TimeSpan.FromSeconds(10); + var tokenSource = new CancellationTokenSource(maximumWaitTime); + tokenSource.Token.Register(() => + { + publish1SyncSource.TrySetResult(false); + publish2SyncSource.TrySetResult(false); + }); + + consumer.Received += async (o, a) => + { + string decoded = Encoding.ASCII.GetString(a.Body.ToArray()); + if (decoded == publish1) + { + publish1SyncSource.TrySetResult(true); + await publish2SyncSource.Task; + } + else if (decoded == publish2) + { + publish2SyncSource.TrySetResult(true); + await publish1SyncSource.Task; + } + }; + + _channel.BasicConsume(q.QueueName, true, consumer); + + // ensure we get a delivery + await Task.WhenAll(publish1SyncSource.Task, publish2SyncSource.Task); + + bool result1 = await publish1SyncSource.Task; + Assert.True(result1, $"1 - Non concurrent dispatch lead to deadlock after {maximumWaitTime}"); + + bool result2 = await publish2SyncSource.Task; + Assert.True(result2, $"2 - Non concurrent dispatch lead to deadlock after {maximumWaitTime}"); + } + + [Fact] + public async Task TestBasicRoundtripConcurrentManyMessages() + { + const int publish_total = 4096; + string queueName = $"{nameof(TestBasicRoundtripConcurrentManyMessages)}-{Guid.NewGuid()}"; + + string publish1 = get_unique_string(32768); + byte[] body1 = Encoding.ASCII.GetBytes(publish1); + string publish2 = get_unique_string(32768); + byte[] body2 = Encoding.ASCII.GetBytes(publish2); + + QueueDeclareOk q = _channel.QueueDeclare(queue: queueName, exclusive: false, durable: true); + Assert.Equal(q.QueueName, queueName); + + Task publishTask = Task.Run(async () => + { + using (IChannel m = _conn.CreateChannel()) + { + QueueDeclareOk q = _channel.QueueDeclare(queue: queueName, exclusive: false, durable: true); + for (int i = 0; i < publish_total; i++) + { + await _channel.BasicPublishAsync(string.Empty, queueName, body1); + await _channel.BasicPublishAsync(string.Empty, queueName, body2); + } + } + }); + + Task consumeTask = Task.Run(async () => + { + var publish1SyncSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var publish2SyncSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var maximumWaitTime = TimeSpan.FromSeconds(30); + var tokenSource = new CancellationTokenSource(maximumWaitTime); + tokenSource.Token.Register(() => + { + publish1SyncSource.TrySetResult(false); + publish2SyncSource.TrySetResult(false); + }); + + using (IChannel m = _conn.CreateChannel()) + { + var consumer = new AsyncEventingBasicConsumer(m); + + int publish1_count = 0; + int publish2_count = 0; + + consumer.Received += async (o, a) => + { + string decoded = Encoding.ASCII.GetString(a.Body.ToArray()); + if (decoded == publish1) + { + if (Interlocked.Increment(ref publish1_count) >= publish_total) + { + publish1SyncSource.TrySetResult(true); + await publish2SyncSource.Task; + } + } + else if (decoded == publish2) + { + if (Interlocked.Increment(ref publish2_count) >= publish_total) + { + publish2SyncSource.TrySetResult(true); + await publish1SyncSource.Task; + } + } + }; + + _channel.BasicConsume(queueName, true, consumer); + + // ensure we get a delivery + await Task.WhenAll(publish1SyncSource.Task, publish2SyncSource.Task); + + bool result1 = await publish1SyncSource.Task; + Assert.True(result1, $"Non concurrent dispatch lead to deadlock after {maximumWaitTime}"); + + bool result2 = await publish2SyncSource.Task; + Assert.True(result2, $"Non concurrent dispatch lead to deadlock after {maximumWaitTime}"); + } + }); + + await Task.WhenAll(publishTask, consumeTask); + } + + [Fact] + public void TestBasicRoundtripNoWait() + { + QueueDeclareOk q = _channel.QueueDeclare(); + byte[] body = _encoding.GetBytes("async-hi"); + _channel.BasicPublish("", q.QueueName, body); + var consumer = new AsyncEventingBasicConsumer(_channel); + var are = new AutoResetEvent(false); + consumer.Received += async (o, a) => + { + are.Set(); + await Task.Yield(); + }; + string tag = _channel.BasicConsume(q.QueueName, true, consumer); + // ensure we get a delivery + bool waitRes = are.WaitOne(2000); + Assert.True(waitRes); + // unsubscribe and ensure no further deliveries + _channel.BasicCancelNoWait(tag); + _channel.BasicPublish("", q.QueueName, body); + bool waitResFalse = are.WaitOne(2000); + Assert.False(waitResFalse); + } + + [Fact] + public async void ConcurrentEventingTestForReceived() + { + const int NumberOfThreads = 4; + const int NumberOfRegistrations = 5000; + + var called = new byte[NumberOfThreads * NumberOfRegistrations]; + + QueueDeclareOk q = _channel.QueueDeclare(); + var consumer = new AsyncEventingBasicConsumer(_channel); + _channel.BasicConsume(q.QueueName, true, consumer); + var countdownEvent = new CountdownEvent(NumberOfThreads); + var tasks = new Task[NumberOfThreads]; + for (int i = 0; i < NumberOfThreads; i++) + { + int threadIndex = i; + tasks[i] = Task.Run(() => + { + countdownEvent.Signal(); + countdownEvent.Wait(); + int start = threadIndex * NumberOfRegistrations; + for (int j = start; j < start + NumberOfRegistrations; j++) + { + int receivedIndex = j; + consumer.Received += (sender, eventArgs) => + { + called[receivedIndex] = 1; + return Task.CompletedTask; + }; + } + }); + } + + countdownEvent.Wait(); + await Task.WhenAll(tasks); + + // Add last receiver + var are = new AutoResetEvent(false); + consumer.Received += (o, a) => + { + are.Set(); + return Task.CompletedTask; + }; + + // Send message + _channel.BasicPublish("", q.QueueName, ReadOnlyMemory.Empty); + are.WaitOne(TimingFixture.TestTimeout); + + // Check received messages + Assert.Equal(-1, called.AsSpan().IndexOf((byte)0)); + } + + [Fact] + public void NonAsyncConsumerShouldThrowInvalidOperationException() + { + QueueDeclareOk q = _channel.QueueDeclare(); + byte[] body = _encoding.GetBytes("async-hi"); + _channel.BasicPublish("", q.QueueName, body); + var consumer = new EventingBasicConsumer(_channel); + Assert.Throws(() => _channel.BasicConsume(q.QueueName, false, consumer)); + } + + private string get_unique_string(int string_length) + { + using (var rng = RandomNumberGenerator.Create()) + { + var bit_count = (string_length * 6); + var byte_count = ((bit_count + 7) / 8); // rounded up + var bytes = new byte[byte_count]; + rng.GetBytes(bytes); + return Convert.ToBase64String(bytes); + } + } + } +} diff --git a/projects/Unit/TestAsyncConsumerExceptions.cs b/projects/Test/Integration/TestAsyncConsumerExceptions.cs similarity index 93% rename from projects/Unit/TestAsyncConsumerExceptions.cs rename to projects/Test/Integration/TestAsyncConsumerExceptions.cs index 610f7388f8..4f0db2490b 100644 --- a/projects/Unit/TestAsyncConsumerExceptions.cs +++ b/projects/Test/Integration/TestAsyncConsumerExceptions.cs @@ -32,11 +32,12 @@ using System; using System.Threading; using System.Threading.Tasks; +using RabbitMQ.Client; using RabbitMQ.Client.Events; using Xunit; using Xunit.Abstractions; -namespace RabbitMQ.Client.Unit +namespace Test.Integration { public class TestAsyncConsumerExceptions : IntegrationFixture { @@ -52,28 +53,26 @@ protected void TestExceptionHandlingWith(IBasicConsumer consumer, var resetEvent = new AutoResetEvent(false); bool notified = false; string q = _channel.QueueDeclare(); - _channel.CallbackException += (m, evt) => { - if (evt.Exception != TestException) return; - - notified = true; - resetEvent.Set(); + if (evt.Exception == TestException) + { + notified = true; + resetEvent.Set(); + } }; string tag = _channel.BasicConsume(q, true, consumer); action(_channel, q, consumer, tag); - resetEvent.WaitOne(2000); + resetEvent.WaitOne(TimeSpan.FromSeconds(2)); Assert.True(notified); } protected override void SetUp() { - _connFactory = new ConnectionFactory - { - DispatchConsumersAsync = true - }; + _connFactory = CreateConnectionFactory(); + _connFactory.DispatchConsumersAsync = true; _conn = _connFactory.CreateConnection(); _channel = _conn.CreateChannel(); diff --git a/projects/Unit/TestAuth.cs b/projects/Test/Integration/TestAuth.cs similarity index 78% rename from projects/Unit/TestAuth.cs rename to projects/Test/Integration/TestAuth.cs index 8761cd35d3..790de61041 100644 --- a/projects/Unit/TestAuth.cs +++ b/projects/Test/Integration/TestAuth.cs @@ -31,20 +31,29 @@ using RabbitMQ.Client.Exceptions; using Xunit; +using Xunit.Abstractions; -namespace RabbitMQ.Client.Unit +namespace Test.Integration { - [Collection("IntegrationFixture")] - public class TestAuth + public class TestAuth : IntegrationFixture { + public TestAuth(ITestOutputHelper output) : base(output) + { + } + + protected override void SetUp() + { + Assert.Null(_connFactory); + Assert.Null(_conn); + Assert.Null(_channel); + } + [Fact] public void TestAuthFailure() { - ConnectionFactory connFactory = new ConnectionFactory - { - UserName = "guest", - Password = "incorrect-password" - }; + var connFactory = CreateConnectionFactory(); + connFactory.UserName = "guest"; + connFactory.Password = "incorrect-password"; try { diff --git a/projects/Unit/TestBasicGet.cs b/projects/Test/Integration/TestBasicGet.cs similarity index 58% rename from projects/Unit/TestBasicGet.cs rename to projects/Test/Integration/TestBasicGet.cs index 5dba9cee8d..9598ecd4a3 100644 --- a/projects/Unit/TestBasicGet.cs +++ b/projects/Test/Integration/TestBasicGet.cs @@ -29,11 +29,14 @@ // Copyright (c) 2007-2020 VMware, Inc. All rights reserved. //--------------------------------------------------------------------------- +using System; +using System.Threading.Tasks; +using RabbitMQ.Client; using RabbitMQ.Client.Exceptions; using Xunit; using Xunit.Abstractions; -namespace RabbitMQ.Client.Unit +namespace Test.Integration { public class TestBasicGet : IntegrationFixture { @@ -74,5 +77,57 @@ public void TestBasicGetWithNonEmptyResponseAndAutoAckMode() AssertMessageCount(queue, 0); }, msg); } + + [Fact] + public async Task TestBasicGetAsync() + { + const string msg = "for async basic.get"; + + QueueDeclareOk queueResult = await _channel.QueueDeclareAsync(string.Empty, false, true, true, true, null); + string queueName = queueResult.QueueName; + + await _channel.BasicPublishAsync(string.Empty, queueName, _encoding.GetBytes(msg), true); + + BasicGetResult getResult = await _channel.BasicGetAsync(queueName, true); + Assert.Equal(msg, _encoding.GetString(getResult.Body.ToArray())); + + QueueDeclareOk queueResultPassive = await _channel.QueueDeclareAsync(queueName, true, true, true, true, null); + Assert.Equal((uint)0, queueResultPassive.MessageCount); + } + + private void EnsureNotEmpty(string q, string body) + { + WithTemporaryChannel(x => x.BasicPublish("", q, _encoding.GetBytes(body))); + } + + private void WithClosedChannel(Action action) + { + IChannel channel = _conn.CreateChannel(); + channel.Close(); + action(channel); + } + + private void WithNonEmptyQueue(Action action) + { + WithNonEmptyQueue(action, "msg"); + } + + private void WithNonEmptyQueue(Action action, string msg) + { + WithTemporaryNonExclusiveQueue((m, q) => + { + EnsureNotEmpty(q, msg); + action(m, q); + }); + } + + private void WithEmptyQueue(Action action) + { + WithTemporaryNonExclusiveQueue((channel, queue) => + { + channel.QueuePurge(queue); + action(channel, queue); + }); + } } } diff --git a/projects/Test/Integration/TestBasicPublish.cs b/projects/Test/Integration/TestBasicPublish.cs new file mode 100644 index 0000000000..f32e3d9224 --- /dev/null +++ b/projects/Test/Integration/TestBasicPublish.cs @@ -0,0 +1,333 @@ +// This source code is dual-licensed under the Apache License, version +// 2.0, and the Mozilla Public License, version 2.0. +// +// The APL v2.0: +// +//--------------------------------------------------------------------------- +// Copyright (c) 2007-2020 VMware, Inc. +// +// 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 +// +// https://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. +//--------------------------------------------------------------------------- +// +// The MPL v2.0: +// +//--------------------------------------------------------------------------- +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 VMware, Inc. or its affiliates. All rights reserved. +//--------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using RabbitMQ.Client; +using RabbitMQ.Client.Events; +using Xunit; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Test.Integration +{ + public class TestBasicPublish : IntegrationFixture + { + public TestBasicPublish(ITestOutputHelper output) : base(output) + { + } + + protected override void SetUp() + { + /* + * NB: the only reason to do a custom SetUp + * is for the MaxMessageSize and QueuePurgeAsync tests, + * which use custom factory settings. This could be improved. + * TODO LRB rabbitmq/rabbitmq-server#1347 + */ + _connFactory = CreateConnectionFactory(); + Assert.Null(_conn); + Assert.Null(_channel); + } + + [Fact] + public void TestBasicRoundtripArray() + { + _conn = _connFactory.CreateConnection(); + _channel = _conn.CreateChannel(); + + QueueDeclareOk q = _channel.QueueDeclare(); + var bp = new BasicProperties(); + byte[] sendBody = _encoding.GetBytes("hi"); + byte[] consumeBody = null; + var consumer = new EventingBasicConsumer(_channel); + var are = new AutoResetEvent(false); + consumer.Received += async (o, a) => + { + consumeBody = a.Body.ToArray(); + are.Set(); + await Task.Yield(); + }; + string tag = _channel.BasicConsume(q.QueueName, true, consumer); + + _channel.BasicPublish("", q.QueueName, bp, sendBody); + bool waitResFalse = are.WaitOne(5000); + _channel.BasicCancel(tag); + + Assert.True(waitResFalse); + Assert.Equal(sendBody, consumeBody); + } + + [Fact] + public void TestBasicRoundtripCachedString() + { + _conn = _connFactory.CreateConnection(); + _channel = _conn.CreateChannel(); + + CachedString exchangeName = new CachedString(string.Empty); + CachedString queueName = new CachedString(_channel.QueueDeclare().QueueName); + byte[] sendBody = _encoding.GetBytes("hi"); + byte[] consumeBody = null; + var consumer = new EventingBasicConsumer(_channel); + var are = new AutoResetEvent(false); + consumer.Received += async (o, a) => + { + consumeBody = a.Body.ToArray(); + are.Set(); + await Task.Yield(); + }; + string tag = _channel.BasicConsume(queueName.Value, true, consumer); + + _channel.BasicPublish(exchangeName, queueName, sendBody); + bool waitResFalse = are.WaitOne(2000); + _channel.BasicCancel(tag); + + Assert.True(waitResFalse); + Assert.Equal(sendBody, consumeBody); + } + + [Fact] + public void TestBasicRoundtripReadOnlyMemory() + { + _conn = _connFactory.CreateConnection(); + _channel = _conn.CreateChannel(); + + QueueDeclareOk q = _channel.QueueDeclare(); + byte[] sendBody = _encoding.GetBytes("hi"); + byte[] consumeBody = null; + var consumer = new EventingBasicConsumer(_channel); + var are = new AutoResetEvent(false); + consumer.Received += async (o, a) => + { + consumeBody = a.Body.ToArray(); + are.Set(); + await Task.Yield(); + }; + string tag = _channel.BasicConsume(q.QueueName, true, consumer); + + _channel.BasicPublish("", q.QueueName, new ReadOnlyMemory(sendBody)); + bool waitResFalse = are.WaitOne(2000); + _channel.BasicCancel(tag); + + Assert.True(waitResFalse); + Assert.Equal(sendBody, consumeBody); + } + + [Fact] + public void CanNotModifyPayloadAfterPublish() + { + _conn = _connFactory.CreateConnection(); + _channel = _conn.CreateChannel(); + + QueueDeclareOk q = _channel.QueueDeclare(); + byte[] sendBody = new byte[1000]; + var consumer = new EventingBasicConsumer(_channel); + var receivedMessage = new AutoResetEvent(false); + bool modified = true; + consumer.Received += (o, a) => + { + if (a.Body.Span.IndexOf((byte)1) < 0) + { + modified = false; + } + receivedMessage.Set(); + }; + string tag = _channel.BasicConsume(q.QueueName, true, consumer); + + _channel.BasicPublish("", q.QueueName, sendBody); + sendBody.AsSpan().Fill(1); + + Assert.True(receivedMessage.WaitOne(5000)); + Assert.False(modified, "Payload was modified after the return of BasicPublish"); + + _channel.BasicCancel(tag); + } + + [Fact] + public void TestMaxMessageSize() + { + var re = new ManualResetEventSlim(); + const ushort maxMsgSize = 1024; + + int count = 0; + byte[] msg0 = _encoding.GetBytes("hi"); + byte[] msg1 = GetRandomBody(maxMsgSize * 2); + + var cf = CreateConnectionFactory(); + cf.AutomaticRecoveryEnabled = false; + cf.TopologyRecoveryEnabled = false; + cf.MaxMessageSize = maxMsgSize; + + bool sawConnectionShutdown = false; + bool sawChannelShutdown = false; + bool sawConsumerRegistered = false; + bool sawConsumerCancelled = false; + + using (IConnection c = cf.CreateConnection()) + { + c.ConnectionShutdown += (o, a) => + { + sawConnectionShutdown = true; + }; + + Assert.Equal(maxMsgSize, cf.MaxMessageSize); + Assert.Equal(maxMsgSize, cf.Endpoint.MaxMessageSize); + Assert.Equal(maxMsgSize, c.Endpoint.MaxMessageSize); + + using (IChannel channel = c.CreateChannel()) + { + channel.ChannelShutdown += (o, a) => + { + sawChannelShutdown = true; + }; + + channel.CallbackException += (o, a) => + { + throw new XunitException("Unexpected channel.CallbackException"); + }; + + QueueDeclareOk q = channel.QueueDeclare(); + + var consumer = new EventingBasicConsumer(channel); + + consumer.Shutdown += (o, a) => + { + re.Set(); + }; + + consumer.Registered += (o, a) => + { + sawConsumerRegistered = true; + }; + + consumer.Unregistered += (o, a) => + { + throw new XunitException("Unexpected consumer.Unregistered"); + }; + + consumer.ConsumerCancelled += (o, a) => + { + sawConsumerCancelled = true; + }; + + consumer.Received += (o, a) => + { + Interlocked.Increment(ref count); + }; + + string tag = channel.BasicConsume(q.QueueName, true, consumer); + + channel.BasicPublish("", q.QueueName, msg0); + channel.BasicPublish("", q.QueueName, msg1); + Assert.True(re.Wait(TimeSpan.FromSeconds(5))); + + Assert.Equal(1, count); + Assert.True(sawConnectionShutdown); + Assert.True(sawChannelShutdown); + Assert.True(sawConsumerRegistered); + Assert.True(sawConsumerCancelled); + } + } + } + + [Fact] + public void TestPropertiesRoundtrip_Headers() + { + _conn = _connFactory.CreateConnection(); + _channel = _conn.CreateChannel(); + + var subject = new BasicProperties + { + Headers = new Dictionary() + }; + + QueueDeclareOk q = _channel.QueueDeclare(); + var bp = new BasicProperties() { Headers = new Dictionary() }; + bp.Headers["Hello"] = "World"; + byte[] sendBody = _encoding.GetBytes("hi"); + byte[] consumeBody = null; + var consumer = new EventingBasicConsumer(_channel); + var are = new AutoResetEvent(false); + string response = null; + consumer.Received += async (o, a) => + { + response = _encoding.GetString(a.BasicProperties.Headers["Hello"] as byte[]); + consumeBody = a.Body.ToArray(); + are.Set(); + await Task.Yield(); + }; + + string tag = _channel.BasicConsume(q.QueueName, true, consumer); + _channel.BasicPublish("", q.QueueName, bp, sendBody); + bool waitResFalse = are.WaitOne(5000); + _channel.BasicCancel(tag); + Assert.True(waitResFalse); + Assert.Equal(sendBody, consumeBody); + Assert.Equal("World", response); + } + + [Fact] + public async Task TestQueuePurgeAsync() + { + const int messageCount = 1024; + + var publishSyncSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + var cf = CreateConnectionFactory(); + cf.DispatchConsumersAsync = true; + + using IConnection connection = cf.CreateConnection(); + using IChannel channel = connection.CreateChannel(); + + await channel.ConfirmSelectAsync(); + + QueueDeclareOk q = await channel.QueueDeclareAsync(string.Empty, false, false, true, false, null); + string queueName = q.QueueName; + + var publishTask = Task.Run(async () => + { + for (int i = 0; i < messageCount; i++) + { + byte[] body = _encoding.GetBytes(Guid.NewGuid().ToString()); + await channel.BasicPublishAsync(string.Empty, queueName, body); + } + publishSyncSource.SetResult(true); + }); + + await channel.WaitForConfirmsOrDieAsync(); + Assert.True(await publishSyncSource.Task); + + Assert.Equal((uint)messageCount, await channel.QueuePurgeAsync(queueName)); + } + } +} diff --git a/projects/Unit/TestChannelAllocation.cs b/projects/Test/Integration/TestChannelAllocation.cs similarity index 66% rename from projects/Unit/TestChannelAllocation.cs rename to projects/Test/Integration/TestChannelAllocation.cs index 3a4671f6bf..bdb3f71a37 100644 --- a/projects/Unit/TestChannelAllocation.cs +++ b/projects/Test/Integration/TestChannelAllocation.cs @@ -31,26 +31,28 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; +using RabbitMQ.Client; using RabbitMQ.Client.Impl; using Xunit; -namespace RabbitMQ.Client.Unit +namespace Test.Integration { - [Collection("IntegrationFixture")] public class TestChannelAllocation : IDisposable { public const int CHANNEL_COUNT = 100; IConnection _c; - public int ChannelNumber(IChannel channel) - { - return ((AutorecoveringChannel)channel).ChannelNumber; - } - public TestChannelAllocation() { - _c = new ConnectionFactory().CreateConnection(); + var cf = new ConnectionFactory + { + ContinuationTimeout = IntegrationFixture.WaitSpan, + HandshakeContinuationTimeout = IntegrationFixture.WaitSpan, + ClientProvidedName = nameof(TestChannelAllocation) + }; + _c = cf.CreateConnection(); } public void Dispose() => _c.Close(); @@ -58,44 +60,63 @@ public TestChannelAllocation() [Fact] public void AllocateInOrder() { + var channels = new List(); for (int i = 1; i <= CHANNEL_COUNT; i++) - Assert.Equal(i, ChannelNumber(_c.CreateChannel())); + { + IChannel channel = _c.CreateChannel(); + channels.Add(channel); + Assert.Equal(i, ChannelNumber(channel)); + } + + foreach (IChannel channel in channels) + { + channel.Dispose(); + } } [Fact] public void AllocateAfterFreeingLast() { - IChannel ch = _c.CreateChannel(); - Assert.Equal(1, ChannelNumber(ch)); - ch.Close(); - ch = _c.CreateChannel(); - Assert.Equal(1, ChannelNumber(ch)); + using IChannel ch0 = _c.CreateChannel(); + Assert.Equal(1, ChannelNumber(ch0)); + ch0.Close(); + + using IChannel ch1 = _c.CreateChannel(); + Assert.Equal(1, ChannelNumber(ch1)); } - public int CompareChannels(IChannel x, IChannel y) + [Fact] + public async Task AllocateAfterFreeingLastAsync() { - int i = ChannelNumber(x); - int j = ChannelNumber(y); - return (i < j) ? -1 : (i == j) ? 0 : 1; + using IChannel ch0 = _c.CreateChannel(); + Assert.Equal(1, ChannelNumber(ch0)); + await ch0.CloseAsync(); + + using IChannel ch1 = _c.CreateChannel(); + Assert.Equal(1, ChannelNumber(ch1)); } [Fact] public void AllocateAfterFreeingMany() { - List channels = new List(); + var channels = new List(); for (int i = 1; i <= CHANNEL_COUNT; i++) + { channels.Add(_c.CreateChannel()); + } foreach (IChannel channel in channels) { channel.Close(); } - channels = new List(); + channels.Clear(); for (int j = 1; j <= CHANNEL_COUNT; j++) + { channels.Add(_c.CreateChannel()); + } // In the current implementation the list should actually // already be sorted, but we don't want to force that behaviour @@ -103,7 +124,22 @@ public void AllocateAfterFreeingMany() int k = 1; foreach (IChannel channel in channels) + { Assert.Equal(k++, ChannelNumber(channel)); + channel.Close(); + } + } + + public int ChannelNumber(IChannel channel) + { + return ((AutorecoveringChannel)channel).ChannelNumber; + } + + public int CompareChannels(IChannel x, IChannel y) + { + int i = ChannelNumber(x); + int j = ChannelNumber(y); + return (i < j) ? -1 : (i == j) ? 0 : 1; } } } diff --git a/projects/Unit/TestChannelShutdown.cs b/projects/Test/Integration/TestChannelShutdown.cs similarity index 94% rename from projects/Unit/TestChannelShutdown.cs rename to projects/Test/Integration/TestChannelShutdown.cs index 04b385fe68..21fbbc6c55 100644 --- a/projects/Unit/TestChannelShutdown.cs +++ b/projects/Test/Integration/TestChannelShutdown.cs @@ -31,11 +31,12 @@ using System; using System.Threading; +using RabbitMQ.Client; using RabbitMQ.Client.Impl; using Xunit; using Xunit.Abstractions; -namespace RabbitMQ.Client.Unit +namespace Test.Integration { public class TestChannelShutdown : IntegrationFixture { @@ -55,7 +56,7 @@ public void TestConsumerDispatcherShutdown() }; Assert.False(m.ConsumerDispatcher.IsShutdown, "dispatcher should NOT be shut down before Close"); _channel.Close(); - Wait(latch, TimeSpan.FromSeconds(3)); + Wait(latch, TimeSpan.FromSeconds(3), "channel shutdown"); Assert.True(m.ConsumerDispatcher.IsShutdown, "dispatcher should be shut down after Close"); } } diff --git a/projects/Unit/TestChannelSoftErrors.cs b/projects/Test/Integration/TestChannelSoftErrors.cs similarity index 98% rename from projects/Unit/TestChannelSoftErrors.cs rename to projects/Test/Integration/TestChannelSoftErrors.cs index 4f0bed6850..b369c6a048 100644 --- a/projects/Unit/TestChannelSoftErrors.cs +++ b/projects/Test/Integration/TestChannelSoftErrors.cs @@ -29,12 +29,13 @@ // Copyright (c) 2007-2020 VMware, Inc. All rights reserved. //--------------------------------------------------------------------------- +using RabbitMQ.Client; using RabbitMQ.Client.Events; using RabbitMQ.Client.Exceptions; using Xunit; using Xunit.Abstractions; -namespace RabbitMQ.Client.Unit +namespace Test.Integration { public class TestChannelSoftErrors : IntegrationFixture { diff --git a/projects/Test/Integration/TestConcurrentAccessWithSharedConnection.cs b/projects/Test/Integration/TestConcurrentAccessWithSharedConnection.cs new file mode 100644 index 0000000000..0519b8d3ec --- /dev/null +++ b/projects/Test/Integration/TestConcurrentAccessWithSharedConnection.cs @@ -0,0 +1,268 @@ +// This source code is dual-licensed under the Apache License, version +// 2.0, and the Mozilla Public License, version 2.0. +// +// The APL v2.0: +// +//--------------------------------------------------------------------------- +// Copyright (c) 2007-2020 VMware, Inc. +// +// 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 +// +// https://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. +//--------------------------------------------------------------------------- +// +// The MPL v2.0: +// +//--------------------------------------------------------------------------- +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2007-2020 VMware, Inc. All rights reserved. +//--------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using RabbitMQ.Client; +using RabbitMQ.Client.Events; +using Xunit; +using Xunit.Abstractions; + +namespace Test.Integration +{ + public class TestConcurrentAccessWithSharedConnection : IntegrationFixture + { + private const ushort _messageCount = 200; + + public TestConcurrentAccessWithSharedConnection(ITestOutputHelper output) : base(output) + { + } + + protected override void SetUp() + { + _connFactory = CreateConnectionFactory(); + _conn = _connFactory.CreateConnection(); + // NB: not creating _channel because this test suite doesn't use it. + Assert.Null(_channel); + } + + [Fact] + public void TestConcurrentChannelOpenAndPublishingWithBlankMessages() + { + TestConcurrentChannelOpenAndPublishingWithBody(Array.Empty(), 30); + } + + [Fact] + public void TestConcurrentChannelOpenAndPublishingSize64() + { + TestConcurrentChannelOpenAndPublishingWithBodyOfSize(64); + } + + [Fact] + public void TestConcurrentChannelOpenAndPublishingSize256() + { + TestConcurrentChannelOpenAndPublishingWithBodyOfSize(256); + } + + [Fact] + public void TestConcurrentChannelOpenAndPublishingSize1024() + { + TestConcurrentChannelOpenAndPublishingWithBodyOfSize(1024); + } + + [Fact] + public Task TestConcurrentChannelOpenAndPublishingWithBlankMessagesAsync() + { + return TestConcurrentChannelOpenAndPublishingWithBodyAsync(Array.Empty(), 30); + } + + [Fact] + public Task TestConcurrentChannelOpenAndPublishingSize64Async() + { + return TestConcurrentChannelOpenAndPublishingWithBodyOfSizeAsync(64); + } + + [Fact] + public Task TestConcurrentChannelOpenAndPublishingSize256Async() + { + return TestConcurrentChannelOpenAndPublishingWithBodyOfSizeAsync(256); + } + + [Fact] + public Task TestConcurrentChannelOpenAndPublishingSize1024Async() + { + return TestConcurrentChannelOpenAndPublishingWithBodyOfSizeAsync(1024); + } + + [Fact] + public void TestConcurrentChannelOpenCloseLoop() + { + TestConcurrentChannelOperations((conn) => + { + using (IChannel ch = conn.CreateChannel()) + { + ch.Close(); + } + }, 50); + } + + private void TestConcurrentChannelOpenAndPublishingWithBodyOfSize(ushort length, int iterations = 30) + { + byte[] body = GetRandomBody(length); + TestConcurrentChannelOpenAndPublishingWithBody(body, iterations); + } + + private Task TestConcurrentChannelOpenAndPublishingWithBodyOfSizeAsync(ushort length, int iterations = 30) + { + byte[] body = GetRandomBody(length); + return TestConcurrentChannelOpenAndPublishingWithBodyAsync(body, iterations); + } + + private void TestConcurrentChannelOpenAndPublishingWithBody(byte[] body, int iterations) + { + TestConcurrentChannelOperations((conn) => + { + using (var localLatch = new ManualResetEvent(false)) + { + // publishing on a shared channel is not supported + // and would missing the point of this test anyway + using (IChannel ch = _conn.CreateChannel()) + { + ch.ConfirmSelect(); + + ch.BasicAcks += (object sender, BasicAckEventArgs e) => + { + if (e.DeliveryTag >= _messageCount) + { + localLatch.Set(); + } + }; + + ch.BasicNacks += (object sender, BasicNackEventArgs e) => + { + localLatch.Set(); + Assert.Fail("should never see a nack"); + }; + + QueueDeclareOk q = ch.QueueDeclare(queue: string.Empty, exclusive: true, autoDelete: true); + for (ushort j = 0; j < _messageCount; j++) + { + ch.BasicPublish("", q.QueueName, body, true); + } + + Assert.True(localLatch.WaitOne(WaitSpan)); + } + } + }, iterations); + } + + /// + /// Tests the concurrent channel open and publishing with body asynchronous. + /// + /// The body. + /// The iterations. + /// + private Task TestConcurrentChannelOpenAndPublishingWithBodyAsync(byte[] body, int iterations) + { + return TestConcurrentChannelOperationsAsync(async (conn) => + { + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var tokenSource = new CancellationTokenSource(LongWaitSpan); + tokenSource.Token.Register(() => + { + tcs.TrySetResult(false); + }); + + using (IChannel ch = _conn.CreateChannel()) + { + await ch.ConfirmSelectAsync(); + + ch.BasicAcks += (object sender, BasicAckEventArgs e) => + { + if (e.DeliveryTag >= _messageCount) + { + tcs.SetResult(true); + } + }; + + ch.BasicNacks += (object sender, BasicNackEventArgs e) => + { + tcs.SetResult(false); + Assert.Fail(String.Format("async test channel saw a nack, deliveryTag: {0}, multiple: {1}", e.DeliveryTag, e.Multiple)); + }; + + QueueDeclareOk q = await ch.QueueDeclareAsync(queue: string.Empty, passive: false, durable: false, exclusive: true, autoDelete: true, arguments: null); + for (ushort j = 0; j < _messageCount; j++) + { + await ch.BasicPublishAsync("", q.QueueName, body, mandatory: true); + } + + Assert.True(await tcs.Task); + } + }, iterations); + } + + private void TestConcurrentChannelOperations(Action actions, int iterations) + { + TestConcurrentChannelOperations(actions, iterations, LongWaitSpan); + } + + private Task TestConcurrentChannelOperationsAsync(Func actions, int iterations) + { + return TestConcurrentChannelOperationsAsync(actions, iterations, LongWaitSpan); + } + + private void TestConcurrentChannelOperations(Action actions, int iterations, TimeSpan timeout) + { + var tasks = new List(); + for (int i = 0; i < _processorCount; i++) + { + tasks.Add(Task.Run(() => + { + for (int j = 0; j < iterations; j++) + { + actions(_conn); + } + })); + } + + Assert.True(Task.WaitAll(tasks.ToArray(), timeout)); + + // incorrect frame interleaving in these tests will result + // in an unrecoverable connection-level exception, thus + // closing the connection + Assert.True(_conn.IsOpen); + } + + private async Task TestConcurrentChannelOperationsAsync(Func actions, int iterations, TimeSpan timeout) + { + var tasks = new List(); + for (int i = 0; i < _processorCount; i++) + { + for (int j = 0; j < iterations; j++) + { + tasks.Add(actions(_conn)); + } + } + + Task t = Task.WhenAll(tasks); + await t; + Assert.Equal(TaskStatus.RanToCompletion, t.Status); + + // incorrect frame interleaving in these tests will result + // in an unrecoverable connection-level exception, thus + // closing the connection + Assert.True(_conn.IsOpen); + } + } +} diff --git a/projects/Unit/TestConfirmSelect.cs b/projects/Test/Integration/TestConfirmSelect.cs similarity index 97% rename from projects/Unit/TestConfirmSelect.cs rename to projects/Test/Integration/TestConfirmSelect.cs index 102282f14f..eb3040f2de 100644 --- a/projects/Unit/TestConfirmSelect.cs +++ b/projects/Test/Integration/TestConfirmSelect.cs @@ -29,10 +29,11 @@ // Copyright (c) 2007-2020 VMware, Inc. All rights reserved. //--------------------------------------------------------------------------- +using RabbitMQ.Client; using Xunit; using Xunit.Abstractions; -namespace RabbitMQ.Client.Unit +namespace Test.Integration { public class TestConfirmSelect : IntegrationFixture { diff --git a/projects/Unit/TestConnectionFactory.cs b/projects/Test/Integration/TestConnectionFactory.cs similarity index 66% rename from projects/Unit/TestConnectionFactory.cs rename to projects/Test/Integration/TestConnectionFactory.cs index 994a18a065..e1c3e356ab 100644 --- a/projects/Unit/TestConnectionFactory.cs +++ b/projects/Test/Integration/TestConnectionFactory.cs @@ -30,14 +30,28 @@ //--------------------------------------------------------------------------- using System.Collections.Generic; +using RabbitMQ.Client; using RabbitMQ.Client.Exceptions; using Xunit; +using Xunit.Abstractions; -namespace RabbitMQ.Client.Unit +namespace Test.Integration { - [Collection("IntegrationFixture")] - public class TestConnectionFactory + public class TestConnectionFactory : IntegrationFixture { + public TestConnectionFactory(ITestOutputHelper output) : base(output) + { + } + + protected override void SetUp() + { + // NB: nothing to do here since each test creates its own factory, + // connections and channels + Assert.Null(_connFactory); + Assert.Null(_conn); + Assert.Null(_channel); + } + [Fact] public void TestProperties() { @@ -73,94 +87,95 @@ public void TestProperties() [Fact] public void TestCreateConnectionUsesSpecifiedPort() { - var cf = new ConnectionFactory - { - AutomaticRecoveryEnabled = true, - HostName = "localhost", - Port = 1234 - }; + var cf = CreateConnectionFactory(); + cf.AutomaticRecoveryEnabled = true; + cf.HostName = "localhost"; + cf.Port = 1234; - Assert.Throws(() => { using IConnection conn = cf.CreateConnection(); }); + Assert.Throws(() => + { + using IConnection conn = cf.CreateConnection(); + }); } [Fact] public void TestCreateConnectionWithClientProvidedNameUsesSpecifiedPort() { - var cf = new ConnectionFactory + var cf = CreateConnectionFactory(); + cf.AutomaticRecoveryEnabled = true; + cf.HostName = "localhost"; + cf.Port = 123; + + Assert.Throws(() => { - AutomaticRecoveryEnabled = true, - HostName = "localhost", - Port = 1234 - }; - Assert.Throws(() => { using IConnection conn = cf.CreateConnection("some_name"); }); + using IConnection conn = cf.CreateConnection(); + }); } [Fact] public void TestCreateConnectionWithClientProvidedNameUsesDefaultName() { - var cf = new ConnectionFactory - { - AutomaticRecoveryEnabled = false, - ClientProvidedName = "some_name" - }; + var cf = CreateConnectionFactory(); + cf.AutomaticRecoveryEnabled = false; + string expectedName = cf.ClientProvidedName; + using (IConnection conn = cf.CreateConnection()) { - Assert.Equal("some_name", conn.ClientProvidedName); - Assert.Equal("some_name", conn.ClientProperties["connection_name"]); + Assert.Equal(expectedName, conn.ClientProvidedName); + Assert.Equal(expectedName, conn.ClientProperties["connection_name"]); } } [Fact] public void TestCreateConnectionWithClientProvidedNameUsesNameArgumentValue() { - var cf = new ConnectionFactory - { - AutomaticRecoveryEnabled = false - }; - using (IConnection conn = cf.CreateConnection("some_name")) + var cf = CreateConnectionFactory(); + cf.AutomaticRecoveryEnabled = false; + string expectedName = cf.ClientProvidedName; + + using (IConnection conn = cf.CreateConnection(expectedName)) { - Assert.Equal("some_name", conn.ClientProvidedName); - Assert.Equal("some_name", conn.ClientProperties["connection_name"]); + Assert.Equal(expectedName, conn.ClientProvidedName); + Assert.Equal(expectedName, conn.ClientProperties["connection_name"]); } } [Fact] public void TestCreateConnectionWithClientProvidedNameAndAutorecoveryUsesNameArgumentValue() { - var cf = new ConnectionFactory - { - AutomaticRecoveryEnabled = true - }; - using (IConnection conn = cf.CreateConnection("some_name")) + var cf = CreateConnectionFactory(); + cf.AutomaticRecoveryEnabled = true; + string expectedName = cf.ClientProvidedName; + + using (IConnection conn = cf.CreateConnection(expectedName)) { - Assert.Equal("some_name", conn.ClientProvidedName); - Assert.Equal("some_name", conn.ClientProperties["connection_name"]); + Assert.Equal(expectedName, conn.ClientProvidedName); + Assert.Equal(expectedName, conn.ClientProperties["connection_name"]); } } [Fact] public void TestCreateConnectionAmqpTcpEndpointListAndClientProvidedName() { - var cf = new ConnectionFactory - { - AutomaticRecoveryEnabled = true - }; + var cf = CreateConnectionFactory(); + cf.AutomaticRecoveryEnabled = true; + string expectedName = cf.ClientProvidedName; + var xs = new List { new AmqpTcpEndpoint("localhost") }; - using (IConnection conn = cf.CreateConnection(xs, "some_name")) + using (IConnection conn = cf.CreateConnection(xs, expectedName)) { - Assert.Equal("some_name", conn.ClientProvidedName); - Assert.Equal("some_name", conn.ClientProperties["connection_name"]); + Assert.Equal(expectedName, conn.ClientProvidedName); + Assert.Equal(expectedName, conn.ClientProperties["connection_name"]); } } [Fact] public void TestCreateConnectionUsesDefaultPort() { - var cf = new ConnectionFactory - { - AutomaticRecoveryEnabled = true, - HostName = "localhost" - }; + var cf = CreateConnectionFactory(); + cf.AutomaticRecoveryEnabled = true; + cf.HostName = "localhost"; + using (IConnection conn = cf.CreateConnection()) { Assert.Equal(5672, conn.Endpoint.Port); @@ -170,11 +185,9 @@ public void TestCreateConnectionUsesDefaultPort() [Fact] public void TestCreateConnectionUsesDefaultMaxMessageSize() { - var cf = new ConnectionFactory - { - AutomaticRecoveryEnabled = true, - HostName = "localhost" - }; + var cf = CreateConnectionFactory(); + cf.AutomaticRecoveryEnabled = true; + cf.HostName = "localhost"; Assert.Equal(ConnectionFactory.DefaultMaxMessageSize, cf.MaxMessageSize); Assert.Equal(ConnectionFactory.DefaultMaxMessageSize, cf.Endpoint.MaxMessageSize); @@ -188,12 +201,11 @@ public void TestCreateConnectionUsesDefaultMaxMessageSize() [Fact] public void TestCreateConnectionWithoutAutoRecoverySelectsAHostFromTheList() { - var cf = new ConnectionFactory - { - AutomaticRecoveryEnabled = false, - HostName = "not_localhost" - }; - IConnection conn = cf.CreateConnection(new List { "localhost" }, "oregano"); + var cf = CreateConnectionFactory(); + cf.AutomaticRecoveryEnabled = false; + cf.HostName = "not_localhost"; + + IConnection conn = cf.CreateConnection(new List { "localhost" }); conn.Close(); conn.Dispose(); Assert.Equal("not_localhost", cf.HostName); @@ -203,12 +215,10 @@ public void TestCreateConnectionWithoutAutoRecoverySelectsAHostFromTheList() [Fact] public void TestCreateConnectionWithAutoRecoveryUsesAmqpTcpEndpoint() { - var cf = new ConnectionFactory - { - AutomaticRecoveryEnabled = true, - HostName = "not_localhost", - Port = 1234 - }; + var cf = CreateConnectionFactory(); + cf.AutomaticRecoveryEnabled = true; + cf.HostName = "not_localhost"; + cf.Port = 1234; var ep = new AmqpTcpEndpoint("localhost"); using (IConnection conn = cf.CreateConnection(new List { ep })) { } } @@ -216,10 +226,8 @@ public void TestCreateConnectionWithAutoRecoveryUsesAmqpTcpEndpoint() [Fact] public void TestCreateConnectionWithAutoRecoveryUsesInvalidAmqpTcpEndpoint() { - var cf = new ConnectionFactory - { - AutomaticRecoveryEnabled = true - }; + var cf = CreateConnectionFactory(); + cf.AutomaticRecoveryEnabled = true; var ep = new AmqpTcpEndpoint("localhost", 1234); Assert.Throws(() => { using IConnection conn = cf.CreateConnection(new List { ep }); }); } @@ -227,11 +235,9 @@ public void TestCreateConnectionWithAutoRecoveryUsesInvalidAmqpTcpEndpoint() [Fact] public void TestCreateConnectionUsesAmqpTcpEndpoint() { - var cf = new ConnectionFactory - { - HostName = "not_localhost", - Port = 1234 - }; + var cf = CreateConnectionFactory(); + cf.HostName = "not_localhost"; + cf.Port = 1234; var ep = new AmqpTcpEndpoint("localhost"); using (IConnection conn = cf.CreateConnection(new List { ep })) { } } @@ -239,33 +245,34 @@ public void TestCreateConnectionUsesAmqpTcpEndpoint() [Fact] public void TestCreateConnectionWithForcedAddressFamily() { - var cf = new ConnectionFactory - { - HostName = "not_localhost" - }; + var cf = CreateConnectionFactory(); + cf.HostName = "not_localhost"; var ep = new AmqpTcpEndpoint("localhost") { AddressFamily = System.Net.Sockets.AddressFamily.InterNetwork }; cf.Endpoint = ep; - using (IConnection conn = cf.CreateConnection()) { }; + using IConnection conn = cf.CreateConnection(); } [Fact] public void TestCreateConnectionUsesInvalidAmqpTcpEndpoint() { - var cf = new ConnectionFactory(); + var cf = CreateConnectionFactory(); var ep = new AmqpTcpEndpoint("localhost", 1234); - Assert.Throws(() => { using (IConnection conn = cf.CreateConnection(new List { ep })) { } }); + Assert.Throws(() => + { + using IConnection conn = cf.CreateConnection(new List { ep }); + }); } [Fact] public void TestCreateConnectioUsesValidEndpointWhenMultipleSupplied() { - var cf = new ConnectionFactory(); + var cf = CreateConnectionFactory(); var invalidEp = new AmqpTcpEndpoint("not_localhost"); var ep = new AmqpTcpEndpoint("localhost"); - using (IConnection conn = cf.CreateConnection(new List { invalidEp, ep })) { }; + using IConnection conn = cf.CreateConnection(new List { invalidEp, ep }); } [Fact] @@ -277,7 +284,7 @@ public void TestCreateAmqpTCPEndPointOverridesMaxMessageSizeWhenGreaterThanMaxim [Fact] public void TestCreateConnectionUsesConfiguredMaxMessageSize() { - var cf = new ConnectionFactory(); + var cf = CreateConnectionFactory(); cf.MaxMessageSize = 1500; using (IConnection conn = cf.CreateConnection()) { @@ -287,7 +294,7 @@ public void TestCreateConnectionUsesConfiguredMaxMessageSize() [Fact] public void TestCreateConnectionWithAmqpEndpointListUsesAmqpTcpEndpointMaxMessageSize() { - var cf = new ConnectionFactory(); + var cf = CreateConnectionFactory(); cf.MaxMessageSize = 1500; var ep = new AmqpTcpEndpoint("localhost"); Assert.Equal(ConnectionFactory.DefaultMaxMessageSize, ep.MaxMessageSize); @@ -300,7 +307,7 @@ public void TestCreateConnectionWithAmqpEndpointListUsesAmqpTcpEndpointMaxMessag [Fact] public void TestCreateConnectionWithAmqpEndpointResolverUsesAmqpTcpEndpointMaxMessageSize() { - var cf = new ConnectionFactory(); + var cf = CreateConnectionFactory(); cf.MaxMessageSize = 1500; var ep = new AmqpTcpEndpoint("localhost", -1, new SslOption(), 1200); using (IConnection conn = cf.CreateConnection(new List { ep })) @@ -312,7 +319,7 @@ public void TestCreateConnectionWithAmqpEndpointResolverUsesAmqpTcpEndpointMaxMe [Fact] public void TestCreateConnectionWithHostnameListUsesConnectionFactoryMaxMessageSize() { - var cf = new ConnectionFactory(); + var cf = CreateConnectionFactory(); cf.MaxMessageSize = 1500; using (IConnection conn = cf.CreateConnection(new List { "localhost" })) { diff --git a/projects/Unit/TestConnectionFactoryContinuationTimeout.cs b/projects/Test/Integration/TestConnectionFactoryContinuationTimeout.cs similarity index 85% rename from projects/Unit/TestConnectionFactoryContinuationTimeout.cs rename to projects/Test/Integration/TestConnectionFactoryContinuationTimeout.cs index 5eb79b4ef8..01a9bd0513 100644 --- a/projects/Unit/TestConnectionFactoryContinuationTimeout.cs +++ b/projects/Test/Integration/TestConnectionFactoryContinuationTimeout.cs @@ -30,10 +30,11 @@ //--------------------------------------------------------------------------- using System; +using RabbitMQ.Client; using Xunit; using Xunit.Abstractions; -namespace RabbitMQ.Client.Unit +namespace Test.Integration { public class TestConnectionFactoryContinuationTimeout : IntegrationFixture { @@ -60,5 +61,13 @@ public void TestConnectionFactoryContinuationTimeoutOnNonRecoveringConnection() Assert.Equal(continuationTimeout, c.CreateChannel().ContinuationTimeout); } } + + private IConnection CreateConnectionWithContinuationTimeout(bool automaticRecoveryEnabled, TimeSpan continuationTimeout) + { + var cf = CreateConnectionFactory(); + cf.AutomaticRecoveryEnabled = automaticRecoveryEnabled; + cf.ContinuationTimeout = continuationTimeout; + return cf.CreateConnection(); + } } } diff --git a/projects/Unit/TestConnectionShutdown.cs b/projects/Test/Integration/TestConnectionShutdown.cs similarity index 84% rename from projects/Unit/TestConnectionShutdown.cs rename to projects/Test/Integration/TestConnectionShutdown.cs index d063742fde..5489ec4611 100644 --- a/projects/Unit/TestConnectionShutdown.cs +++ b/projects/Test/Integration/TestConnectionShutdown.cs @@ -31,12 +31,13 @@ using System; using System.Threading; +using RabbitMQ.Client; using RabbitMQ.Client.Framing.Impl; using RabbitMQ.Client.Impl; using Xunit; using Xunit.Abstractions; -namespace RabbitMQ.Client.Unit +namespace Test.Integration { public class TestConnectionShutdown : IntegrationFixture { @@ -47,9 +48,6 @@ public TestConnectionShutdown(ITestOutputHelper output) : base(output) [Fact] public void TestCleanClosureWithSocketClosedOutOfBand() { - _conn = CreateAutorecoveringConnection(); - _channel = _conn.CreateChannel(); - var latch = new ManualResetEventSlim(false); _channel.ChannelShutdown += (channel, args) => { @@ -57,18 +55,15 @@ public void TestCleanClosureWithSocketClosedOutOfBand() }; var c = (AutorecoveringConnection)_conn; - c.FrameHandler.Close(); + c.CloseFrameHandler(); _conn.Close(TimeSpan.FromSeconds(4)); - Wait(latch, TimeSpan.FromSeconds(5)); + Wait(latch, TimeSpan.FromSeconds(5), "channel shutdown"); } [Fact] public void TestAbortWithSocketClosedOutOfBand() { - _conn = CreateAutorecoveringConnection(); - _channel = _conn.CreateChannel(); - var latch = new ManualResetEventSlim(false); _channel.ChannelShutdown += (channel, args) => { @@ -76,19 +71,16 @@ public void TestAbortWithSocketClosedOutOfBand() }; var c = (AutorecoveringConnection)_conn; - c.FrameHandler.Close(); + c.CloseFrameHandler(); _conn.Abort(); // default Connection.Abort() timeout and then some - Wait(latch, TimeSpan.FromSeconds(6)); + Wait(latch, TimeSpan.FromSeconds(6), "channel shutdown"); } [Fact] public void TestDisposedWithSocketClosedOutOfBand() { - _conn = CreateAutorecoveringConnection(); - _channel = _conn.CreateChannel(); - var latch = new ManualResetEventSlim(false); _channel.ChannelShutdown += (channel, args) => { @@ -96,10 +88,10 @@ public void TestDisposedWithSocketClosedOutOfBand() }; var c = (AutorecoveringConnection)_conn; - c.FrameHandler.Close(); + c.CloseFrameHandler(); _conn.Dispose(); - Wait(latch, TimeSpan.FromSeconds(3)); + Wait(latch, TimeSpan.FromSeconds(3), "channel shutdown"); } [Fact] @@ -113,7 +105,7 @@ public void TestShutdownSignalPropagationToChannels() }; _conn.Close(); - Wait(latch, TimeSpan.FromSeconds(3)); + Wait(latch, TimeSpan.FromSeconds(3), "channel shutdown"); } [Fact] @@ -128,7 +120,7 @@ public void TestConsumerDispatcherShutdown() }; Assert.False(m.ConsumerDispatcher.IsShutdown, "dispatcher should NOT be shut down before Close"); _conn.Close(); - Wait(latch, TimeSpan.FromSeconds(3)); + Wait(latch, TimeSpan.FromSeconds(3), "channel shutdown"); Assert.True(m.ConsumerDispatcher.IsShutdown, "dispatcher should be shut down after Close"); } } diff --git a/projects/Test/Integration/TestConsumer.cs b/projects/Test/Integration/TestConsumer.cs new file mode 100644 index 0000000000..42db2cb6f6 --- /dev/null +++ b/projects/Test/Integration/TestConsumer.cs @@ -0,0 +1,297 @@ +// This source code is dual-licensed under the Apache License, version +// 2.0, and the Mozilla Public License, version 2.0. +// +// The APL v2.0: +// +//--------------------------------------------------------------------------- +// Copyright (c) 2007-2020 VMware, Inc. +// +// 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 +// +// https://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. +//--------------------------------------------------------------------------- +// +// The MPL v2.0: +// +//--------------------------------------------------------------------------- +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2007-2020 VMware, Inc. All rights reserved. +//--------------------------------------------------------------------------- + +using System; +using System.Threading; +using System.Threading.Tasks; +using RabbitMQ.Client; +using RabbitMQ.Client.Events; +using Xunit; +using Xunit.Abstractions; + +namespace Test.Integration +{ + public class TestConsumer : IntegrationFixture + { + private readonly ShutdownEventArgs _closeArgs = new ShutdownEventArgs(ShutdownInitiator.Application, Constants.ReplySuccess, "normal shutdown"); + + public TestConsumer(ITestOutputHelper output) : base(output) + { + } + + protected override void SetUp() + { + // NB: nothing to do here + Assert.Null(_connFactory); + Assert.Null(_conn); + Assert.Null(_channel); + } + + [Fact] + public async Task TestBasicRoundtripConcurrent() + { + var cf = CreateConnectionFactory(); + cf.ConsumerDispatchConcurrency = 2; + + using IConnection conn = cf.CreateConnection(); + using IChannel channel = conn.CreateChannel(); + + QueueDeclareOk q = channel.QueueDeclare(); + const string publish1 = "sync-hi-1"; + byte[] body = _encoding.GetBytes(publish1); + channel.BasicPublish("", q.QueueName, body); + const string publish2 = "sync-hi-2"; + body = _encoding.GetBytes(publish2); + channel.BasicPublish("", q.QueueName, body); + + var consumer = new EventingBasicConsumer(channel); + + var publish1SyncSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var publish2SyncSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var maximumWaitTime = TimeSpan.FromSeconds(10); + var tokenSource = new CancellationTokenSource(maximumWaitTime); + tokenSource.Token.Register(() => + { + publish1SyncSource.TrySetResult(false); + publish2SyncSource.TrySetResult(false); + }); + + consumer.Received += (o, a) => + { + switch (_encoding.GetString(a.Body.ToArray())) + { + case publish1: + publish1SyncSource.TrySetResult(true); + break; + case publish2: + publish2SyncSource.TrySetResult(true); + break; + } + }; + + channel.BasicConsume(q.QueueName, true, consumer); + + await Task.WhenAll(publish1SyncSource.Task, publish2SyncSource.Task); + + bool result1 = await publish1SyncSource.Task; + Assert.True(result1, $"Non concurrent dispatch lead to deadlock after {maximumWaitTime}"); + + bool result2 = await publish1SyncSource.Task; + Assert.True(result2, $"Non concurrent dispatch lead to deadlock after {maximumWaitTime}"); + + // Note: closing channel explicitly just to test it. + await channel.CloseAsync(_closeArgs, false); + } + + [Fact] + public async Task TestBasicRejectAsync() + { + var publishSyncSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + var cf = CreateConnectionFactory(); + cf.DispatchConsumersAsync = true; + + using IConnection connection = cf.CreateConnection(); + using IChannel channel = connection.CreateChannel(); + + var consumer = new AsyncEventingBasicConsumer(channel); + consumer.Received += async (object sender, BasicDeliverEventArgs args) => + { + var c = sender as AsyncEventingBasicConsumer; + Assert.NotNull(c); + await channel.BasicCancelAsync(c.ConsumerTags[0]); + await channel.BasicRejectAsync(args.DeliveryTag, true); + publishSyncSource.SetResult(true); + }; + + QueueDeclareOk q = await channel.QueueDeclareAsync(string.Empty, false, false, true, false, null); + string queueName = q.QueueName; + const string publish1 = "sync-hi-1"; + byte[] body = _encoding.GetBytes(publish1); + await channel.BasicPublishAsync(string.Empty, queueName, body); + + await channel.BasicConsumeAsync(queue: queueName, autoAck: false, + consumerTag: string.Empty, noLocal: false, exclusive: false, + arguments: null, consumer); + + Assert.True(await publishSyncSource.Task); + + uint messageCount, consumerCount = 0; + ushort tries = 5; + do + { + QueueDeclareOk result = await channel.QueueDeclareAsync(queue: queueName, passive: true, false, false, false, null); + consumerCount = result.ConsumerCount; + messageCount = result.MessageCount; + if (consumerCount == 0 && messageCount > 0) + { + break; + } + else + { + await Task.Delay(500); + } + } while (tries-- > 0); + + if (tries == 0) + { + Assert.Fail("[ERROR] failed waiting for MessageCount > 0 && ConsumerCount == 0"); + } + else + { + Assert.Equal((uint)1, messageCount); + Assert.Equal((uint)0, consumerCount); + } + + // Note: closing channel explicitly just to test it. + await channel.CloseAsync(_closeArgs, false); + } + + [Fact] + public async Task TestBasicAckAsync() + { + const int messageCount = 1024; + int messagesReceived = 0; + + var publishSyncSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + var cf = CreateConnectionFactory(); + cf.DispatchConsumersAsync = true; + + using IConnection connection = cf.CreateConnection(); + using IChannel channel = connection.CreateChannel(); + + await channel.ConfirmSelectAsync(); + + var consumer = new AsyncEventingBasicConsumer(channel); + consumer.Received += async (object sender, BasicDeliverEventArgs args) => + { + var c = sender as AsyncEventingBasicConsumer; + Assert.NotNull(c); + await channel.BasicAckAsync(args.DeliveryTag, false); + messagesReceived++; + if (messagesReceived == messageCount) + { + publishSyncSource.SetResult(true); + } + }; + + QueueDeclareOk q = await channel.QueueDeclareAsync(string.Empty, false, false, true, false, null); + string queueName = q.QueueName; + + await channel.BasicQosAsync(0, 1, false); + await channel.BasicConsumeAsync(queue: queueName, autoAck: false, + consumerTag: string.Empty, noLocal: false, exclusive: false, + arguments: null, consumer); + + var publishTask = Task.Run(async () => + { + for (int i = 0; i < messageCount; i++) + { + byte[] body = _encoding.GetBytes(Guid.NewGuid().ToString()); + await channel.BasicPublishAsync(string.Empty, queueName, body); + } + }); + + await channel.WaitForConfirmsOrDieAsync(); + Assert.True(await publishSyncSource.Task); + + Assert.Equal(messageCount, messagesReceived); + + // Note: closing channel explicitly just to test it. + await channel.CloseAsync(_closeArgs, false); + } + + [Fact] + public async Task TestBasicNackAsync() + { + var publishSyncSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + var cf = CreateConnectionFactory(); + cf.DispatchConsumersAsync = true; + + using IConnection connection = cf.CreateConnection(); + using IChannel channel = connection.CreateChannel(); + + var consumer = new AsyncEventingBasicConsumer(channel); + consumer.Received += async (object sender, BasicDeliverEventArgs args) => + { + var c = sender as AsyncEventingBasicConsumer; + Assert.NotNull(c); + await channel.BasicCancelAsync(c.ConsumerTags[0]); + await channel.BasicNackAsync(args.DeliveryTag, false, true); + publishSyncSource.SetResult(true); + }; + + QueueDeclareOk q = await channel.QueueDeclareAsync(string.Empty, false, false, true, false, null); + string queueName = q.QueueName; + const string publish1 = "sync-hi-1"; + byte[] body = _encoding.GetBytes(publish1); + await channel.BasicPublishAsync(string.Empty, queueName, body); + + await channel.BasicConsumeAsync(queue: queueName, autoAck: false, + consumerTag: string.Empty, noLocal: false, exclusive: false, + arguments: null, consumer); + + Assert.True(await publishSyncSource.Task); + + uint messageCount, consumerCount = 0; + ushort tries = 5; + do + { + QueueDeclareOk result = await channel.QueueDeclareAsync(queue: queueName, passive: true, false, false, false, null); + consumerCount = result.ConsumerCount; + messageCount = result.MessageCount; + if (consumerCount == 0 && messageCount > 0) + { + break; + } + else + { + await Task.Delay(500); + } + } while (tries-- > 0); + + if (tries == 0) + { + Assert.Fail("[ERROR] failed waiting for MessageCount > 0 && ConsumerCount == 0"); + } + else + { + Assert.Equal((uint)1, messageCount); + Assert.Equal((uint)0, consumerCount); + } + + // Note: closing channel explicitly just to test it. + await channel.CloseAsync(_closeArgs, false); + } + } +} diff --git a/projects/Unit/TestConsumerCancelNotify.cs b/projects/Test/Integration/TestConsumerCancelNotify.cs similarity index 99% rename from projects/Unit/TestConsumerCancelNotify.cs rename to projects/Test/Integration/TestConsumerCancelNotify.cs index f298852965..152ed8e9f5 100644 --- a/projects/Unit/TestConsumerCancelNotify.cs +++ b/projects/Test/Integration/TestConsumerCancelNotify.cs @@ -31,11 +31,12 @@ using System.Linq; using System.Threading; +using RabbitMQ.Client; using RabbitMQ.Client.Events; using Xunit; using Xunit.Abstractions; -namespace RabbitMQ.Client.Unit +namespace Test.Integration { public class TestConsumerCancelNotify : IntegrationFixture { diff --git a/projects/Unit/TestConsumerCount.cs b/projects/Test/Integration/TestConsumerCount.cs similarity index 97% rename from projects/Unit/TestConsumerCount.cs rename to projects/Test/Integration/TestConsumerCount.cs index 1f846e8621..8b38ea0360 100644 --- a/projects/Unit/TestConsumerCount.cs +++ b/projects/Test/Integration/TestConsumerCount.cs @@ -29,11 +29,12 @@ // Copyright (c) 2007-2020 VMware, Inc. All rights reserved. //--------------------------------------------------------------------------- +using RabbitMQ.Client; using RabbitMQ.Client.Events; using Xunit; using Xunit.Abstractions; -namespace RabbitMQ.Client.Unit +namespace Test.Integration { public class TestConsumerCount : IntegrationFixture { diff --git a/projects/Unit/TestConsumerExceptions.cs b/projects/Test/Integration/TestConsumerExceptions.cs similarity index 99% rename from projects/Unit/TestConsumerExceptions.cs rename to projects/Test/Integration/TestConsumerExceptions.cs index 5bf62391fc..e856e380d8 100644 --- a/projects/Unit/TestConsumerExceptions.cs +++ b/projects/Test/Integration/TestConsumerExceptions.cs @@ -31,10 +31,11 @@ using System; using System.Threading; +using RabbitMQ.Client; using Xunit; using Xunit.Abstractions; -namespace RabbitMQ.Client.Unit +namespace Test.Integration { public class TestConsumerExceptions : IntegrationFixture { diff --git a/projects/Unit/TestConsumerOperationDispatch.cs b/projects/Test/Integration/TestConsumerOperationDispatch.cs similarity index 91% rename from projects/Unit/TestConsumerOperationDispatch.cs rename to projects/Test/Integration/TestConsumerOperationDispatch.cs index 1f3a1129e2..19f853155e 100644 --- a/projects/Unit/TestConsumerOperationDispatch.cs +++ b/projects/Test/Integration/TestConsumerOperationDispatch.cs @@ -32,32 +32,33 @@ using System; using System.Collections.Generic; using System.Threading; +using RabbitMQ.Client; using RabbitMQ.Client.Events; using Xunit; using Xunit.Abstractions; -namespace RabbitMQ.Client.Unit +namespace Test.Integration { public class TestConsumerOperationDispatch : IntegrationFixture { - public TestConsumerOperationDispatch(ITestOutputHelper output) : base(output) - { - } + // number of channels (and consumers) + private const int Y = 100; + // number of messages to be published + private const int N = 100; + + private static readonly CountdownEvent counter = new CountdownEvent(Y); - private readonly string _x = "dotnet.tests.consumer-operation-dispatch.fanout"; + private const string _x = "dotnet.tests.consumer-operation-dispatch.fanout"; private readonly List _channels = new List(); private readonly List _queues = new List(); private readonly List _consumers = new List(); - // number of channels (and consumers) - private const int Y = 100; - // number of messages to be published - private const int N = 100; - - public static CountdownEvent counter = new CountdownEvent(Y); + public TestConsumerOperationDispatch(ITestOutputHelper output) : base(output) + { + } - public override void Dispose() + protected override void TearDown() { foreach (IChannel ch in _channels) { @@ -67,10 +68,7 @@ public override void Dispose() } } - _queues.Clear(); - _consumers.Clear(); counter.Reset(); - base.ReleaseResources(); } private class CollectingConsumer : DefaultBasicConsumer @@ -104,8 +102,7 @@ public override void HandleBasicDeliver(string consumerTag, [Fact] public void TestDeliveryOrderingWithSingleChannel() { - IChannel Ch = _conn.CreateChannel(); - Ch.ExchangeDeclare(_x, "fanout", durable: false); + _channel.ExchangeDeclare(_x, "fanout", durable: false); for (int i = 0; i < Y; i++) { @@ -121,9 +118,10 @@ public void TestDeliveryOrderingWithSingleChannel() for (int i = 0; i < N; i++) { - Ch.BasicPublish(_x, "", _encoding.GetBytes("msg")); + _channel.BasicPublish(_x, "", _encoding.GetBytes("msg")); } - counter.Wait(TimeSpan.FromSeconds(120)); + + counter.Wait(TimeSpan.FromMinutes(2)); foreach (CollectingConsumer cons in _consumers) { @@ -145,9 +143,10 @@ public void TestDeliveryOrderingWithSingleChannel() [Fact] public void TestChannelShutdownDoesNotShutDownDispatcher() { + _channel.ExchangeDeclare(_x, "fanout", durable: false); + IChannel ch1 = _conn.CreateChannel(); IChannel ch2 = _conn.CreateChannel(); - _channel.ExchangeDeclare(_x, "fanout", durable: false); string q1 = ch1.QueueDeclare().QueueName; string q2 = ch2.QueueDeclare().QueueName; @@ -165,7 +164,7 @@ public void TestChannelShutdownDoesNotShutDownDispatcher() ch1.Close(); ch2.BasicPublish(_x, "", _encoding.GetBytes("msg")); - Wait(latch); + Wait(latch, "received event"); } private class ShutdownLatchConsumer : DefaultBasicConsumer @@ -203,7 +202,7 @@ public void TestChannelShutdownHandler() _channel.BasicConsume(queue: q, autoAck: true, consumer: c); _channel.Close(); - Wait(latch, TimeSpan.FromSeconds(5)); + Wait(latch, TimeSpan.FromSeconds(5), "channel shutdown"); Assert.False(duplicateLatch.Wait(TimeSpan.FromSeconds(5)), "event handler fired more than once"); } diff --git a/projects/Unit/TestEventingConsumer.cs b/projects/Test/Integration/TestEventingConsumer.cs similarity index 96% rename from projects/Unit/TestEventingConsumer.cs rename to projects/Test/Integration/TestEventingConsumer.cs index 6660af4f13..c12da36c8f 100644 --- a/projects/Unit/TestEventingConsumer.cs +++ b/projects/Test/Integration/TestEventingConsumer.cs @@ -30,11 +30,12 @@ //--------------------------------------------------------------------------- using System.Threading; +using RabbitMQ.Client; using RabbitMQ.Client.Events; using Xunit; using Xunit.Abstractions; -namespace RabbitMQ.Client.Unit +namespace Test.Integration { public class TestEventingConsumer : IntegrationFixture { @@ -66,14 +67,14 @@ public void TestEventingConsumerRegistrationEvents() }; string tag = _channel.BasicConsume(q, false, ec); - Wait(registeredLatch); + Wait(registeredLatch, "consumer registered"); Assert.NotNull(registeredSender); Assert.Equal(ec, registeredSender); Assert.Equal(_channel, ((EventingBasicConsumer)registeredSender).Channel); _channel.BasicCancel(tag); - Wait(unregisteredLatch); + Wait(unregisteredLatch, "consumer unregistered"); Assert.NotNull(unregisteredSender); Assert.Equal(ec, unregisteredSender); Assert.Equal(_channel, ((EventingBasicConsumer)unregisteredSender).Channel); diff --git a/projects/Unit/TestExceptionMessages.cs b/projects/Test/Integration/TestExceptionMessages.cs similarity index 98% rename from projects/Unit/TestExceptionMessages.cs rename to projects/Test/Integration/TestExceptionMessages.cs index 2f87a9f430..7901096f54 100644 --- a/projects/Unit/TestExceptionMessages.cs +++ b/projects/Test/Integration/TestExceptionMessages.cs @@ -34,7 +34,7 @@ using Xunit; using Xunit.Abstractions; -namespace RabbitMQ.Client.Unit +namespace Test.Integration { public class TestExceptionMessages : IntegrationFixture { diff --git a/projects/Unit/TestExchangeDeclare.cs b/projects/Test/Integration/TestExchangeDeclare.cs similarity index 99% rename from projects/Unit/TestExchangeDeclare.cs rename to projects/Test/Integration/TestExchangeDeclare.cs index 8fdf3e6fd4..fe1c728807 100644 --- a/projects/Unit/TestExchangeDeclare.cs +++ b/projects/Test/Integration/TestExchangeDeclare.cs @@ -33,10 +33,11 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using RabbitMQ.Client; using Xunit; using Xunit.Abstractions; -namespace RabbitMQ.Client.Unit +namespace Test.Integration { public class TestExchangeDeclare : IntegrationFixture { diff --git a/projects/Unit/TestExtensions.cs b/projects/Test/Integration/TestExtensions.cs similarity index 93% rename from projects/Unit/TestExtensions.cs rename to projects/Test/Integration/TestExtensions.cs index 2392011edb..69c1b0be57 100644 --- a/projects/Unit/TestExtensions.cs +++ b/projects/Test/Integration/TestExtensions.cs @@ -31,10 +31,11 @@ using System; using System.Threading.Tasks; +using RabbitMQ.Client; using Xunit; using Xunit.Abstractions; -namespace RabbitMQ.Client.Unit +namespace Test.Integration { public class TestExtensions : IntegrationFixture { @@ -54,9 +55,9 @@ public async Task TestConfirmBarrier() } [Fact] - public async Task TestConfirmBeforeWait() + public Task TestConfirmBeforeWait() { - await Assert.ThrowsAsync(async () => await _channel.WaitForConfirmsAsync()); + return Assert.ThrowsAsync(() => _channel.WaitForConfirmsAsync()); } [Fact] diff --git a/projects/Test/Integration/TestFloodPublishing.cs b/projects/Test/Integration/TestFloodPublishing.cs new file mode 100644 index 0000000000..752f2b24a7 --- /dev/null +++ b/projects/Test/Integration/TestFloodPublishing.cs @@ -0,0 +1,148 @@ +// This source code is dual-licensed under the Apache License, version +// 2.0, and the Mozilla Public License, version 2.0. +// +// The APL v2.0: +// +//--------------------------------------------------------------------------- +// Copyright (c) 2007-2020 VMware, Inc. +// +// 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 +// +// https://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. +//--------------------------------------------------------------------------- +// +// The MPL v2.0: +// +//--------------------------------------------------------------------------- +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2007-2020 VMware, Inc. All rights reserved. +//--------------------------------------------------------------------------- + +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using RabbitMQ.Client; +using RabbitMQ.Client.Events; +using Xunit; +using Xunit.Abstractions; + +namespace Test.Integration +{ + public class TestFloodPublishing : IntegrationFixture + { + private readonly byte[] _body = new byte[2048]; + private readonly TimeSpan _tenSeconds = TimeSpan.FromSeconds(10); + + public TestFloodPublishing(ITestOutputHelper output) : base(output) + { + } + + protected override void SetUp() + { + _connFactory = CreateConnectionFactory(); + _connFactory.RequestedHeartbeat = TimeSpan.FromSeconds(60); + _connFactory.AutomaticRecoveryEnabled = false; + + _conn = _connFactory.CreateConnection(); + _channel = _conn.CreateChannel(); + } + + [Fact] + public async Task TestUnthrottledFloodPublishingAsync() + { + _conn.ConnectionShutdown += (_, args) => + { + if (args.Initiator != ShutdownInitiator.Application) + { + Assert.Fail("Unexpected connection shutdown!"); + } + }; + + var stopwatch = Stopwatch.StartNew(); + int i = 0; + try + { + for (i = 0; i < 65535 * 64; i++) + { + if (i % 65536 == 0) + { + if (stopwatch.Elapsed > _tenSeconds) + { + break; + } + } + + await _channel.BasicPublishAsync(CachedString.Empty, CachedString.Empty, _body); + } + } + finally + { + stopwatch.Stop(); + } + + Assert.True(_conn.IsOpen); + } + + [Fact] + public async Task TestMultithreadFloodPublishingAsync() + { + string message = "Hello from test TestMultithreadFloodPublishing"; + byte[] sendBody = _encoding.GetBytes(message); + int publishCount = 4096; + int receivedCount = 0; + var autoResetEvent = new AutoResetEvent(false); + + var cf = CreateConnectionFactory(); + cf.RequestedHeartbeat = TimeSpan.FromSeconds(60); + cf.AutomaticRecoveryEnabled = false; + + string queueName = null; + QueueDeclareOk q = _channel.QueueDeclare(); + queueName = q.QueueName; + + Task pub = Task.Run(async () => + { + using (IChannel pubCh = _conn.CreateChannel()) + { + for (int i = 0; i < publishCount; i++) + { + await pubCh.BasicPublishAsync(string.Empty, queueName, sendBody); + } + } + }); + + using (IChannel consumeCh = _conn.CreateChannel()) + { + var consumer = new EventingBasicConsumer(consumeCh); + consumer.Received += (o, a) => + { + string receivedMessage = _encoding.GetString(a.Body.ToArray()); + Assert.Equal(message, receivedMessage); + Interlocked.Increment(ref receivedCount); + if (receivedCount == publishCount) + { + autoResetEvent.Set(); + } + }; + consumeCh.BasicConsume(queueName, true, consumer); + + Assert.True(autoResetEvent.WaitOne(_tenSeconds)); + } + + await pub; + Assert.Equal(publishCount, receivedCount); + } + } +} diff --git a/projects/Unit/TestHeartbeats.cs b/projects/Test/Integration/TestHeartbeats.cs similarity index 62% rename from projects/Unit/TestHeartbeats.cs rename to projects/Test/Integration/TestHeartbeats.cs index 40deb350f7..86e1600821 100644 --- a/projects/Unit/TestHeartbeats.cs +++ b/projects/Test/Integration/TestHeartbeats.cs @@ -31,34 +31,43 @@ using System; using System.Collections.Generic; -using System.IO; using System.Threading; +using RabbitMQ.Client; using Xunit; using Xunit.Abstractions; -namespace RabbitMQ.Client.Unit +namespace Test.Integration { public class TestHeartbeats : IntegrationFixture { + private readonly TimeSpan _heartbeatTimeout = TimeSpan.FromSeconds(2); + public TestHeartbeats(ITestOutputHelper output) : base(output) { } - private readonly TimeSpan _heartbeatTimeout = TimeSpan.FromSeconds(2); + protected override void SetUp() + { + Assert.Null(_connFactory); + Assert.Null(_conn); + Assert.Null(_channel); + } - [Fact(Timeout = 35000)] + [SkippableFact(Timeout = 35000)] [Trait("Category", "LongRunning")] public void TestThatHeartbeatWriterUsesConfigurableInterval() { - var cf = new ConnectionFactory() - { - RequestedHeartbeat = _heartbeatTimeout, - AutomaticRecoveryEnabled = false - }; + Skip.IfNot(LongRunningTestsEnabled(), "RABBITMQ_LONG_RUNNING_TESTS is not set, skipping test"); + + var cf = CreateConnectionFactory(); + cf.RequestedHeartbeat = _heartbeatTimeout; + cf.AutomaticRecoveryEnabled = false; + RunSingleConnectionTest(cf); } [SkippableFact] + [Trait("Category", "LongRunning")] public void TestThatHeartbeatWriterWithTLSEnabled() { Skip.IfNot(LongRunningTestsEnabled(), "RABBITMQ_LONG_RUNNING_TESTS is not set, skipping test"); @@ -66,12 +75,10 @@ public void TestThatHeartbeatWriterWithTLSEnabled() var sslEnv = new SslEnv(); Skip.IfNot(sslEnv.IsSslConfigured, "SSL_CERTS_DIR and/or PASSWORD are not configured, skipping test"); - var cf = new ConnectionFactory() - { - Port = 5671, - RequestedHeartbeat = _heartbeatTimeout, - AutomaticRecoveryEnabled = false - }; + var cf = CreateConnectionFactory(); + cf.Port = 5671; + cf.RequestedHeartbeat = _heartbeatTimeout; + cf.AutomaticRecoveryEnabled = false; cf.Ssl.ServerName = sslEnv.Hostname; cf.Ssl.CertPath = sslEnv.CertPath; @@ -81,43 +88,59 @@ public void TestThatHeartbeatWriterWithTLSEnabled() RunSingleConnectionTest(cf); } - [Fact(Timeout = 90000)] + [SkippableFact(Timeout = 90000)] [Trait("Category", "LongRunning")] public void TestHundredsOfConnectionsWithRandomHeartbeatInterval() { - var rnd = new Random(); - List xs = new List(); - // Since we are using the ThreadPool, let's set MinThreads to a high-enough value. - ThreadPool.SetMinThreads(200, 200); - for (int i = 0; i < 200; i++) + Skip.IfNot(LongRunningTestsEnabled(), "RABBITMQ_LONG_RUNNING_TESTS is not set, skipping test"); + + const ushort connectionCount = 200; + + ThreadPool.GetMinThreads(out int origWorkerThreads, out int origCompletionPortThreads); + try { - ushort n = Convert.ToUInt16(rnd.Next(2, 6)); - var cf = new ConnectionFactory() + var rnd = new Random(); + var conns = new List(); + + // Since we are using the ThreadPool, let's set MinThreads to a high-enough value. + ThreadPool.SetMinThreads(connectionCount, connectionCount); + + try { - RequestedHeartbeat = TimeSpan.FromSeconds(n), - AutomaticRecoveryEnabled = false - }; - IConnection conn = cf.CreateConnection(); - xs.Add(conn); - IChannel ch = conn.CreateChannel(); - - conn.ConnectionShutdown += (sender, evt) => + for (int i = 0; i < connectionCount; i++) { - CheckInitiator(evt); - }; + ushort n = Convert.ToUInt16(rnd.Next(2, 6)); + var cf = CreateConnectionFactory(); + cf.RequestedHeartbeat = TimeSpan.FromSeconds(n); + cf.AutomaticRecoveryEnabled = false; + + IConnection conn = cf.CreateConnection($"_testDisplayName:{i}"); + conns.Add(conn); + IChannel ch = conn.CreateChannel(); + conn.ConnectionShutdown += (sender, evt) => + { + CheckInitiator(evt); + }; + } + SleepFor(60); + } + finally + { + foreach (IConnection conn in conns) + { + conn.Close(); + } + } } - - SleepFor(60); - - foreach (IConnection x in xs) + finally { - x.Close(); + Assert.True(ThreadPool.SetMinThreads(origWorkerThreads, origCompletionPortThreads)); } } - protected void RunSingleConnectionTest(ConnectionFactory cf) + private void RunSingleConnectionTest(ConnectionFactory cf) { - using (IConnection conn = cf.CreateConnection()) + using (IConnection conn = cf.CreateConnection(_testDisplayName)) { using (IChannel ch = conn.CreateChannel()) { @@ -145,11 +168,18 @@ protected void RunSingleConnectionTest(ConnectionFactory cf) private bool LongRunningTestsEnabled() { string s = Environment.GetEnvironmentVariable("RABBITMQ_LONG_RUNNING_TESTS"); + if (String.IsNullOrEmpty(s)) { return false; } - return true; + + if (Boolean.TryParse(s, out bool enabled)) + { + return enabled; + } + + return false; } private void SleepFor(int t) diff --git a/projects/Unit/TestInitialConnection.cs b/projects/Test/Integration/TestInitialConnection.cs similarity index 88% rename from projects/Unit/TestInitialConnection.cs rename to projects/Test/Integration/TestInitialConnection.cs index f20afb9bb9..b0a34e0d36 100644 --- a/projects/Unit/TestInitialConnection.cs +++ b/projects/Test/Integration/TestInitialConnection.cs @@ -30,11 +30,12 @@ //--------------------------------------------------------------------------- using System.Collections.Generic; +using RabbitMQ.Client; using RabbitMQ.Client.Exceptions; using Xunit; using Xunit.Abstractions; -namespace RabbitMQ.Client.Unit +namespace Test.Integration { public class TestInitialConnection : IntegrationFixture { @@ -45,7 +46,7 @@ public TestInitialConnection(ITestOutputHelper output) : base(output) [Fact] public void TestBasicConnectionRecoveryWithHostnameList() { - Framing.Impl.AutorecoveringConnection c = CreateAutorecoveringConnection(new List() { "127.0.0.1", "localhost" }); + var c = CreateAutorecoveringConnection(new List() { "127.0.0.1", "localhost" }); Assert.True(c.IsOpen); c.Close(); } @@ -53,7 +54,7 @@ public void TestBasicConnectionRecoveryWithHostnameList() [Fact] public void TestBasicConnectionRecoveryWithHostnameListAndUnreachableHosts() { - Framing.Impl.AutorecoveringConnection c = CreateAutorecoveringConnection(new List() { "191.72.44.22", "127.0.0.1", "localhost" }); + var c = CreateAutorecoveringConnection(new List() { "191.72.44.22", "127.0.0.1", "localhost" }); Assert.True(c.IsOpen); c.Close(); } diff --git a/projects/Unit/TestInvalidAck.cs b/projects/Test/Integration/TestInvalidAck.cs similarity index 97% rename from projects/Unit/TestInvalidAck.cs rename to projects/Test/Integration/TestInvalidAck.cs index 0d3d5dc750..427e8a9342 100644 --- a/projects/Unit/TestInvalidAck.cs +++ b/projects/Test/Integration/TestInvalidAck.cs @@ -30,10 +30,11 @@ //--------------------------------------------------------------------------- using System.Threading; +using RabbitMQ.Client; using Xunit; using Xunit.Abstractions; -namespace RabbitMQ.Client.Unit +namespace Test.Integration { public class TestInvalidAck : IntegrationFixture { diff --git a/projects/Unit/TestMainLoop.cs b/projects/Test/Integration/TestMainLoop.cs similarity index 82% rename from projects/Unit/TestMainLoop.cs rename to projects/Test/Integration/TestMainLoop.cs index 597f1898f3..5eb3798b40 100644 --- a/projects/Unit/TestMainLoop.cs +++ b/projects/Test/Integration/TestMainLoop.cs @@ -31,11 +31,12 @@ using System; using System.Threading; +using RabbitMQ.Client; using RabbitMQ.Client.Events; using Xunit; using Xunit.Abstractions; -namespace RabbitMQ.Client.Unit +namespace Test.Integration { public class TestMainLoop : IntegrationFixture { @@ -62,27 +63,24 @@ public override void HandleBasicDeliver(string consumerTag, [Fact] public void TestCloseWithFaultyConsumer() { - ConnectionFactory connFactory = new ConnectionFactory(); - IConnection c = connFactory.CreateConnection(); - IChannel m = _conn.CreateChannel(); object o = new object(); string q = GenerateQueueName(); - m.QueueDeclare(q, false, false, false, null); + _channel.QueueDeclare(q, false, false, false, null); CallbackExceptionEventArgs ea = null; - m.CallbackException += (_, evt) => + _channel.CallbackException += (_, evt) => { ea = evt; - c.Close(); + _channel.Close(); Monitor.PulseAll(o); }; - m.BasicConsume(q, true, new FaultyConsumer(_channel)); - m.BasicPublish("", q, _encoding.GetBytes("message")); + _channel.BasicConsume(q, true, new FaultyConsumer(_channel)); + _channel.BasicPublish("", q, _encoding.GetBytes("message")); WaitOn(o); Assert.NotNull(ea); - Assert.False(c.IsOpen); - Assert.Equal(200, c.CloseReason.ReplyCode); + Assert.False(_channel.IsOpen); + Assert.Equal(200, _channel.CloseReason.ReplyCode); } } } diff --git a/projects/Unit/TestMessageCount.cs b/projects/Test/Integration/TestMessageCount.cs similarity index 97% rename from projects/Unit/TestMessageCount.cs rename to projects/Test/Integration/TestMessageCount.cs index ccee33b724..acbf70a6c7 100644 --- a/projects/Unit/TestMessageCount.cs +++ b/projects/Test/Integration/TestMessageCount.cs @@ -30,10 +30,11 @@ //--------------------------------------------------------------------------- using System.Threading.Tasks; +using RabbitMQ.Client; using Xunit; using Xunit.Abstractions; -namespace RabbitMQ.Client.Unit +namespace Test.Integration { public class TestMessageCount : IntegrationFixture { diff --git a/projects/Unit/TestNowait.cs b/projects/Test/Integration/TestNowait.cs similarity index 98% rename from projects/Unit/TestNowait.cs rename to projects/Test/Integration/TestNowait.cs index 33df3c69d4..4fa6f06b30 100644 --- a/projects/Unit/TestNowait.cs +++ b/projects/Test/Integration/TestNowait.cs @@ -29,10 +29,11 @@ // Copyright (c) 2007-2020 VMware, Inc. All rights reserved. //--------------------------------------------------------------------------- +using RabbitMQ.Client; using Xunit; using Xunit.Abstractions; -namespace RabbitMQ.Client.Unit +namespace Test.Integration { public class TestNoWait : IntegrationFixture { diff --git a/projects/Unit/TestPassiveDeclare.cs b/projects/Test/Integration/TestPassiveDeclare.cs similarity index 98% rename from projects/Unit/TestPassiveDeclare.cs rename to projects/Test/Integration/TestPassiveDeclare.cs index b4871d75bb..6063697159 100644 --- a/projects/Unit/TestPassiveDeclare.cs +++ b/projects/Test/Integration/TestPassiveDeclare.cs @@ -34,7 +34,7 @@ using Xunit; using Xunit.Abstractions; -namespace RabbitMQ.Client.Unit +namespace Test.Integration { public class TestPassiveDeclare : IntegrationFixture { diff --git a/projects/Unit/TestPublishSharedChannel.cs b/projects/Test/Integration/TestPublishSharedChannel.cs similarity index 83% rename from projects/Unit/TestPublishSharedChannel.cs rename to projects/Test/Integration/TestPublishSharedChannel.cs index aab3f9db4c..3477d7f802 100644 --- a/projects/Unit/TestPublishSharedChannel.cs +++ b/projects/Test/Integration/TestPublishSharedChannel.cs @@ -32,12 +32,13 @@ using System; using System.Threading; using System.Threading.Tasks; +using RabbitMQ.Client; using Xunit; +using Xunit.Abstractions; -namespace RabbitMQ.Client.Unit +namespace Test.Integration { - [Collection("IntegrationFixture")] - public class TestPublishSharedChannel + public class TestPublishSharedChannel : IntegrationFixture { private const string QueueName = "TestPublishSharedChannel_Queue"; private static readonly CachedString ExchangeName = new CachedString("TestPublishSharedChannel_Ex"); @@ -49,17 +50,26 @@ public class TestPublishSharedChannel private Exception _raisedException; + public TestPublishSharedChannel(ITestOutputHelper output) : base(output) + { + } + + protected override void SetUp() + { + // NB: test sets up its own factory, conns, channels + Assert.Null(_connFactory); + Assert.Null(_conn); + Assert.Null(_channel); + } + [Fact] public async Task MultiThreadPublishOnSharedChannel() { - // Arrange - var connFactory = new ConnectionFactory - { - RequestedHeartbeat = TimeSpan.FromSeconds(60), - AutomaticRecoveryEnabled = false - }; + var cf = CreateConnectionFactory(); + cf.RequestedHeartbeat = TimeSpan.FromSeconds(60); + cf.AutomaticRecoveryEnabled = false; - using (IConnection conn = connFactory.CreateConnection()) + using (IConnection conn = cf.CreateConnection()) { conn.ConnectionShutdown += (_, args) => { diff --git a/projects/Unit/TestPublisherConfirms.cs b/projects/Test/Integration/TestPublisherConfirms.cs similarity index 66% rename from projects/Unit/TestPublisherConfirms.cs rename to projects/Test/Integration/TestPublisherConfirms.cs index 4f910b8784..d5d59a8202 100644 --- a/projects/Unit/TestPublisherConfirms.cs +++ b/projects/Test/Integration/TestPublisherConfirms.cs @@ -33,40 +33,37 @@ using System.Reflection; using System.Threading; using System.Threading.Tasks; +using RabbitMQ.Client; using RabbitMQ.Client.Impl; using Xunit; using Xunit.Abstractions; -namespace RabbitMQ.Client.Unit +// TODO rabbitmq/rabbitmq-dotnet-client#1347 +// Test mixed sync/async with WaitForConfirmsAsync +namespace Test.Integration { public class TestPublisherConfirms : IntegrationFixture { - private const string QueueName = "RabbitMQ.Client.Unit.TestPublisherConfirms"; - private readonly byte[] _body = new byte[4096]; + private readonly byte[] _messageBody; public TestPublisherConfirms(ITestOutputHelper output) : base(output) { -#if NET6_0_OR_GREATER - Random.Shared.NextBytes(_body); -#else - var rnd = new Random(); - rnd.NextBytes(_body); -#endif + _messageBody = GetRandomBody(4096); } [Fact] - public void TestWaitForConfirmsWithoutTimeout() + public Task TestWaitForConfirmsWithoutTimeoutAsync() { - TestWaitForConfirms(200, async (ch) => + return TestWaitForConfirmsAsync(200, async (ch) => { Assert.True(await ch.WaitForConfirmsAsync()); }); } [Fact] - public void TestWaitForConfirmsWithTimeout() + public Task TestWaitForConfirmsWithTimeout() { - TestWaitForConfirms(200, async (ch) => + return TestWaitForConfirmsAsync(200, async (ch) => { using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(4))) { @@ -76,12 +73,12 @@ public void TestWaitForConfirmsWithTimeout() } [Fact] - public void TestWaitForConfirmsWithTimeout_MightThrowTaskCanceledException() + public async Task TestWaitForConfirmsWithTimeoutAsync_MightThrowTaskCanceledException() { bool waitResult = false; - bool sawTaskCanceled = false; + bool sawException = false; - TestWaitForConfirms(10000, async (ch) => + Task t = TestWaitForConfirmsAsync(10000, async (ch) => { using (var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(1))) { @@ -89,23 +86,25 @@ public void TestWaitForConfirmsWithTimeout_MightThrowTaskCanceledException() { waitResult = await ch.WaitForConfirmsAsync(cts.Token); } - catch (TaskCanceledException) + catch { - sawTaskCanceled = true; + sawException = true; } } }); - if (waitResult == false && sawTaskCanceled == false) + await t; + + if (waitResult == false && sawException == false) { - Assert.Fail("test failed, both waitResult and sawTaskCanceled are still false"); + Assert.Fail("test failed, both waitResult and sawException are still false"); } } [Fact] - public void TestWaitForConfirmsWithTimeout_MessageNacked_WaitingHasTimedout_ReturnFalse() + public Task TestWaitForConfirmsWithTimeoutAsync_MessageNacked_WaitingHasTimedout_ReturnFalse() { - TestWaitForConfirms(2000, async (ch) => + return TestWaitForConfirmsAsync(2000, async (ch) => { IChannel actualChannel = ((AutorecoveringChannel)ch).InnerChannel; actualChannel @@ -121,13 +120,14 @@ public void TestWaitForConfirmsWithTimeout_MessageNacked_WaitingHasTimedout_Retu } [Fact] - public async Task TestWaitForConfirmsWithEvents() + public async Task TestWaitForConfirmsWithEventsAsync() { + string queueName = string.Format("{0}:{1}", _testDisplayName, Guid.NewGuid()); using (IChannel ch = _conn.CreateChannel()) { - ch.ConfirmSelect(); + await ch.ConfirmSelectAsync(); + await ch.QueueDeclareAsync(queue: queueName, passive: false, durable: false, exclusive: false, autoDelete: false, arguments: null); - ch.QueueDeclare(QueueName); int n = 200; // number of event handler invocations int c = 0; @@ -136,12 +136,14 @@ public async Task TestWaitForConfirmsWithEvents() { Interlocked.Increment(ref c); }; + try { for (int i = 0; i < n; i++) { - ch.BasicPublish("", QueueName, _encoding.GetBytes("msg")); + await ch.BasicPublishAsync("", queueName, _encoding.GetBytes("msg")); } + await ch.WaitForConfirmsAsync(); // Note: number of event invocations is not guaranteed @@ -152,32 +154,33 @@ public async Task TestWaitForConfirmsWithEvents() } finally { - ch.QueueDelete(QueueName); + await ch.QueueDeleteAsync(queue: queueName, ifUnused: false, ifEmpty: false); } } } - protected void TestWaitForConfirms(int numberOfMessagesToPublish, Action fn) + private async Task TestWaitForConfirmsAsync(int numberOfMessagesToPublish, Func fn) { + string queueName = string.Format("{0}:{1}", _testDisplayName, Guid.NewGuid()); using (IChannel ch = _conn.CreateChannel()) { var props = new BasicProperties { Persistent = true }; - ch.ConfirmSelect(); - ch.QueueDeclare(QueueName); + await ch.ConfirmSelectAsync(); + await ch.QueueDeclareAsync(queue: queueName, passive: false, durable: false, exclusive: false, autoDelete: false, arguments: null); for (int i = 0; i < numberOfMessagesToPublish; i++) { - ch.BasicPublish(exchange: "", routingKey: QueueName, body: _body, mandatory: true, basicProperties: props); + await ch.BasicPublishAsync(exchange: string.Empty, routingKey: queueName, body: _messageBody, mandatory: true, basicProperties: props); } try { - fn(ch); + await fn(ch); } finally { - ch.QueueDelete(QueueName); + await ch.QueueDeleteAsync(queue: queueName, ifUnused: false, ifEmpty: false); } } } diff --git a/projects/Unit/TestQueueDeclare.cs b/projects/Test/Integration/TestQueueDeclare.cs similarity index 97% rename from projects/Unit/TestQueueDeclare.cs rename to projects/Test/Integration/TestQueueDeclare.cs index d6c9c99816..0bb872c102 100644 --- a/projects/Unit/TestQueueDeclare.cs +++ b/projects/Test/Integration/TestQueueDeclare.cs @@ -33,10 +33,11 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using RabbitMQ.Client; using Xunit; using Xunit.Abstractions; -namespace RabbitMQ.Client.Unit +namespace Test.Integration { public class TestQueueDeclare : IntegrationFixture { @@ -167,6 +168,8 @@ async Task f() QueueDeclareOk r = await _channel.QueueDeclareAsync(qname, passive: true, false, false, false, null); Assert.Equal(qname, r.QueueName); + await _channel.QueueUnbindAsync(queue: qname, exchange: "amq.fanout", routingKey: qname, null); + uint deletedMessageCount = await _channel.QueueDeleteAsync(qname, false, false); Assert.Equal((uint)0, deletedMessageCount); } diff --git a/projects/Unit/TestSsl.cs b/projects/Test/Integration/TestSsl.cs similarity index 71% rename from projects/Unit/TestSsl.cs rename to projects/Test/Integration/TestSsl.cs index 4695b188e5..570c2b9c5f 100644 --- a/projects/Unit/TestSsl.cs +++ b/projects/Test/Integration/TestSsl.cs @@ -29,39 +29,39 @@ // Copyright (c) 2007-2020 VMware, Inc. All rights reserved. //--------------------------------------------------------------------------- -using System; using System.IO; using System.Net.Security; -using System.Reflection; using System.Security.Authentication; +using RabbitMQ.Client; using Xunit; using Xunit.Abstractions; -namespace RabbitMQ.Client.Unit +namespace Test.Integration { - [Collection("IntegrationFixture")] - public class TestSsl + public class TestSsl : IntegrationFixture { - private readonly ITestOutputHelper _output; - private readonly string _testDisplayName; private readonly SslEnv _sslEnv; - public TestSsl(ITestOutputHelper output) + public TestSsl(ITestOutputHelper output) : base(output) { - _output = output; - var type = _output.GetType(); - var testMember = type.GetField("test", BindingFlags.Instance | BindingFlags.NonPublic); - var test = (ITest)testMember.GetValue(output); - _testDisplayName = test.DisplayName; _sslEnv = new SslEnv(); } + protected override void SetUp() + { + Assert.Null(_connFactory); + Assert.Null(_conn); + Assert.Null(_channel); + } + [SkippableFact] public void TestServerVerifiedIgnoringNameMismatch() { Skip.IfNot(_sslEnv.IsSslConfigured, "SSL_CERTS_DIR and/or PASSWORD are not configured, skipping test"); - ConnectionFactory cf = new ConnectionFactory { Port = 5671 }; + var cf = CreateConnectionFactory(); + cf.Port = 5671; + cf.Ssl.ServerName = "*"; cf.Ssl.AcceptablePolicyErrors = SslPolicyErrors.RemoteCertificateNameMismatch; cf.Ssl.Enabled = true; @@ -73,7 +73,8 @@ public void TestServerVerified() { Skip.IfNot(_sslEnv.IsSslConfigured, "SSL_CERTS_DIR and/or PASSWORD are not configured, skipping test"); - ConnectionFactory cf = new ConnectionFactory { Port = 5671 }; + var cf = CreateConnectionFactory(); + cf.Port = 5671; cf.Ssl.ServerName = _sslEnv.Hostname; cf.Ssl.Enabled = true; SendReceive(cf); @@ -85,10 +86,10 @@ public void TestClientAndServerVerified() Skip.IfNot(_sslEnv.IsSslConfigured, "SSL_CERTS_DIR and/or PASSWORD are not configured, skipping test"); string certPath = _sslEnv.CertPath; - _output.WriteLine($"[INFO] certPath: {certPath}"); Assert.True(File.Exists(certPath)); - ConnectionFactory cf = new ConnectionFactory { Port = 5671 }; + var cf = CreateConnectionFactory(); + cf.Port = 5671; cf.Ssl.ServerName = _sslEnv.Hostname; cf.Ssl.CertPath = certPath; cf.Ssl.CertPassphrase = _sslEnv.CertPassphrase; @@ -102,19 +103,17 @@ public void TestNoClientCertificate() { Skip.IfNot(_sslEnv.IsSslConfigured, "SSL_CERTS_DIR and/or PASSWORD are not configured, skipping test"); - ConnectionFactory cf = new ConnectionFactory + var cf = CreateConnectionFactory(); + cf.Port = 5671; + cf.Ssl = new SslOption() { - Port = 5671, - Ssl = new SslOption() - { - CertPath = null, - Enabled = true, - ServerName = _sslEnv.Hostname, - Version = SslProtocols.None, - AcceptablePolicyErrors = - SslPolicyErrors.RemoteCertificateNotAvailable | - SslPolicyErrors.RemoteCertificateNameMismatch - } + CertPath = null, + Enabled = true, + ServerName = _sslEnv.Hostname, + Version = SslProtocols.None, + AcceptablePolicyErrors = + SslPolicyErrors.RemoteCertificateNotAvailable | + SslPolicyErrors.RemoteCertificateNameMismatch }; SendReceive(cf); @@ -122,7 +121,7 @@ public void TestNoClientCertificate() private void SendReceive(ConnectionFactory cf) { - using (IConnection conn = cf.CreateConnection($"{_testDisplayName}:{Guid.NewGuid()}")) + using (IConnection conn = cf.CreateConnection(_testDisplayName)) { using (IChannel ch = conn.CreateChannel()) { @@ -131,13 +130,13 @@ private void SendReceive(ConnectionFactory cf) ch.QueueBind(qName, "Exchange_TestSslEndPoint", "Key_TestSslEndpoint", null); string message = "Hello C# SSL Client World"; - byte[] msgBytes = System.Text.Encoding.UTF8.GetBytes(message); + byte[] msgBytes = _encoding.GetBytes(message); ch.BasicPublish("Exchange_TestSslEndPoint", "Key_TestSslEndpoint", msgBytes); bool autoAck = false; BasicGetResult result = ch.BasicGet(qName, autoAck); byte[] body = result.Body.ToArray(); - string resultMessage = System.Text.Encoding.UTF8.GetString(body); + string resultMessage = _encoding.GetString(body); Assert.Equal(message, resultMessage); } diff --git a/projects/Unit/TestUpdateSecret.cs b/projects/Test/Integration/TestUpdateSecret.cs similarity index 95% rename from projects/Unit/TestUpdateSecret.cs rename to projects/Test/Integration/TestUpdateSecret.cs index b81ef5ce15..d2396ca831 100644 --- a/projects/Unit/TestUpdateSecret.cs +++ b/projects/Test/Integration/TestUpdateSecret.cs @@ -31,7 +31,7 @@ using Xunit.Abstractions; -namespace RabbitMQ.Client.Unit +namespace Test.Integration { public class TestUpdateSecret : IntegrationFixture { @@ -39,7 +39,6 @@ public TestUpdateSecret(ITestOutputHelper output) : base(output) { } - [IgnoreOnVersionsEarlierThan(3, 8)] public void TestUpdatingConnectionSecret() { _conn.UpdateSecret("new-secret", "Test Case"); diff --git a/projects/Test/Integration/TimingFixture.cs b/projects/Test/Integration/TimingFixture.cs new file mode 100644 index 0000000000..65ca598f35 --- /dev/null +++ b/projects/Test/Integration/TimingFixture.cs @@ -0,0 +1,46 @@ +// This source code is dual-licensed under the Apache License, version +// 2.0, and the Mozilla Public License, version 2.0. +// +// The APL v2.0: +// +//--------------------------------------------------------------------------- +// Copyright (c) 2007-2020 VMware, Inc. +// +// 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 +// +// https://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. +//--------------------------------------------------------------------------- +// +// The MPL v2.0: +// +//--------------------------------------------------------------------------- +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2007-2020 VMware, Inc. All rights reserved. +//--------------------------------------------------------------------------- + +using System; + +namespace Test.Integration +{ + public static class TimingFixture + { + public static readonly TimeSpan TimingInterval = TimeSpan.FromMilliseconds(300); + public static readonly TimeSpan TimingInterval_2X = TimeSpan.FromMilliseconds(600); + public static readonly TimeSpan TimingInterval_4X = TimeSpan.FromMilliseconds(1200); + public static readonly TimeSpan TimingInterval_8X = TimeSpan.FromMilliseconds(2400); + public static readonly TimeSpan TimingInterval_16X = TimeSpan.FromMilliseconds(4800); + public static readonly TimeSpan SafetyMargin = TimeSpan.FromMilliseconds(150); + public static readonly TimeSpan TestTimeout = TimeSpan.FromSeconds(5); + } +} diff --git a/projects/OAuth2Test/APIApproval.Approve.verified.txt b/projects/Test/OAuth2/APIApproval.Approve.verified.txt similarity index 100% rename from projects/OAuth2Test/APIApproval.Approve.verified.txt rename to projects/Test/OAuth2/APIApproval.Approve.verified.txt diff --git a/projects/OAuth2Test/APIApproval.cs b/projects/Test/OAuth2/APIApproval.cs similarity index 100% rename from projects/OAuth2Test/APIApproval.cs rename to projects/Test/OAuth2/APIApproval.cs diff --git a/projects/OAuth2Test/OAuth2Test.csproj b/projects/Test/OAuth2/OAuth2.csproj similarity index 79% rename from projects/OAuth2Test/OAuth2Test.csproj rename to projects/Test/OAuth2/OAuth2.csproj index e262304e8f..72ed6fce76 100644 --- a/projects/OAuth2Test/OAuth2Test.csproj +++ b/projects/Test/OAuth2/OAuth2.csproj @@ -9,15 +9,15 @@ - ../rabbit.snk + ../../rabbit.snk true latest 7.0 - - + + @@ -27,15 +27,15 @@ all - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + diff --git a/projects/OAuth2Test/README.md b/projects/Test/OAuth2/README.md similarity index 100% rename from projects/OAuth2Test/README.md rename to projects/Test/OAuth2/README.md diff --git a/projects/OAuth2Test/RequestFormMatcher.cs b/projects/Test/OAuth2/RequestFormMatcher.cs similarity index 100% rename from projects/OAuth2Test/RequestFormMatcher.cs rename to projects/Test/OAuth2/RequestFormMatcher.cs diff --git a/projects/OAuth2Test/TestOAuth2.cs b/projects/Test/OAuth2/TestOAuth2.cs similarity index 99% rename from projects/OAuth2Test/TestOAuth2.cs rename to projects/Test/OAuth2/TestOAuth2.cs index bb968407de..ba9db4a41c 100644 --- a/projects/OAuth2Test/TestOAuth2.cs +++ b/projects/Test/OAuth2/TestOAuth2.cs @@ -79,7 +79,8 @@ public TestOAuth2(ITestOutputHelper testOutputHelper) { AutomaticRecoveryEnabled = true, CredentialsProvider = GetCredentialsProvider(options), - CredentialsRefresher = GetCredentialsRefresher() + CredentialsRefresher = GetCredentialsRefresher(), + ClientProvidedName = nameof(TestOAuth2) }; _connection = connectionFactory.CreateConnection(); diff --git a/projects/OAuth2Test/TestOAuth2Client.cs b/projects/Test/OAuth2/TestOAuth2Client.cs similarity index 100% rename from projects/OAuth2Test/TestOAuth2Client.cs rename to projects/Test/OAuth2/TestOAuth2Client.cs diff --git a/projects/OAuth2Test/TestOAuth2ClientCredentialsProvider.cs b/projects/Test/OAuth2/TestOAuth2ClientCredentialsProvider.cs similarity index 100% rename from projects/OAuth2Test/TestOAuth2ClientCredentialsProvider.cs rename to projects/Test/OAuth2/TestOAuth2ClientCredentialsProvider.cs diff --git a/projects/OAuth2Test/enabled_plugins b/projects/Test/OAuth2/enabled_plugins similarity index 100% rename from projects/OAuth2Test/enabled_plugins rename to projects/Test/OAuth2/enabled_plugins diff --git a/projects/OAuth2Test/keycloak/import/test-realm.json b/projects/Test/OAuth2/keycloak/import/test-realm.json similarity index 100% rename from projects/OAuth2Test/keycloak/import/test-realm.json rename to projects/Test/OAuth2/keycloak/import/test-realm.json diff --git a/projects/OAuth2Test/keycloak/rabbitmq.conf b/projects/Test/OAuth2/keycloak/rabbitmq.conf similarity index 100% rename from projects/OAuth2Test/keycloak/rabbitmq.conf rename to projects/Test/OAuth2/keycloak/rabbitmq.conf diff --git a/projects/OAuth2Test/keycloak/signing-key/signing-key.pem b/projects/Test/OAuth2/keycloak/signing-key/signing-key.pem similarity index 100% rename from projects/OAuth2Test/keycloak/signing-key/signing-key.pem rename to projects/Test/OAuth2/keycloak/signing-key/signing-key.pem diff --git a/projects/OAuth2Test/uaa/log4j2.properties b/projects/Test/OAuth2/uaa/log4j2.properties similarity index 100% rename from projects/OAuth2Test/uaa/log4j2.properties rename to projects/Test/OAuth2/uaa/log4j2.properties diff --git a/projects/OAuth2Test/uaa/rabbitmq.conf b/projects/Test/OAuth2/uaa/rabbitmq.conf similarity index 100% rename from projects/OAuth2Test/uaa/rabbitmq.conf rename to projects/Test/OAuth2/uaa/rabbitmq.conf diff --git a/projects/OAuth2Test/uaa/signing-key/signing-key.pem b/projects/Test/OAuth2/uaa/signing-key/signing-key.pem similarity index 100% rename from projects/OAuth2Test/uaa/signing-key/signing-key.pem rename to projects/Test/OAuth2/uaa/signing-key/signing-key.pem diff --git a/projects/OAuth2Test/uaa/uaa.yml b/projects/Test/OAuth2/uaa/uaa.yml similarity index 100% rename from projects/OAuth2Test/uaa/uaa.yml rename to projects/Test/OAuth2/uaa/uaa.yml diff --git a/projects/Test/SequentialIntegration/SequentialIntegration.csproj b/projects/Test/SequentialIntegration/SequentialIntegration.csproj new file mode 100644 index 0000000000..6cda456251 --- /dev/null +++ b/projects/Test/SequentialIntegration/SequentialIntegration.csproj @@ -0,0 +1,51 @@ + + + + net6.0;net472 + + + + net6.0 + + + + ../../rabbit.snk + true + latest + 7.0 + true + + + + + + + + + + + <_Parameter1>Xunit.CollectionBehavior.CollectionPerAssembly + <_Parameter1_IsLiteral>true + <_Parameter1_TypeName>Xunit.CollectionBehavior.CollectionPerAssembly + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + diff --git a/projects/Test/SequentialIntegration/SequentialIntegrationFixture.cs b/projects/Test/SequentialIntegration/SequentialIntegrationFixture.cs new file mode 100644 index 0000000000..9d769bfc8f --- /dev/null +++ b/projects/Test/SequentialIntegration/SequentialIntegrationFixture.cs @@ -0,0 +1,89 @@ +// This source code is dual-licensed under the Apache License, version +// 2.0, and the Mozilla Public License, version 2.0. +// +// The APL v2.0: +// +//--------------------------------------------------------------------------- +// Copyright (c) 2007-2020 VMware, Inc. +// +// 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 +// +// https://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. +//--------------------------------------------------------------------------- +// +// The MPL v2.0: +// +//--------------------------------------------------------------------------- +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2007-2020 VMware, Inc. All rights reserved. +//--------------------------------------------------------------------------- + +using System; +using System.Threading; +using RabbitMQ.Client; +using Xunit.Abstractions; + +namespace Test.SequentialIntegration +{ + public class SequentialIntegrationFixture : IntegrationFixture + { + public SequentialIntegrationFixture(ITestOutputHelper output) : base(output) + { + } + + public void Block() + { + _rabbitMQCtl.ExecRabbitMQCtl("set_vm_memory_high_watermark 0.000000001"); + // give rabbitmqctl some time to do its job + Thread.Sleep(TimeSpan.FromSeconds(2)); + Publish(); + } + + public void Unblock() + { + _rabbitMQCtl.ExecRabbitMQCtl("set_vm_memory_high_watermark 0.4"); + } + + public void RestartRabbitMQ() + { + StopRabbitMQ(); + Thread.Sleep(TimeSpan.FromMilliseconds(500)); + StartRabbitMQ(); + AwaitRabbitMQ(); + } + + public void StopRabbitMQ() + { + _rabbitMQCtl.ExecRabbitMQCtl("stop_app"); + } + + public void StartRabbitMQ() + { + _rabbitMQCtl.ExecRabbitMQCtl("start_app"); + } + + private void AwaitRabbitMQ() + { + _rabbitMQCtl.ExecRabbitMQCtl("await_startup"); + } + + private void Publish() + { + using (IChannel ch = _conn.CreateChannel()) + { + ch.BasicPublish("amq.fanout", "", _encoding.GetBytes("message")); + } + } + } +} diff --git a/projects/Unit/TestConnectionBlocked.cs b/projects/Test/SequentialIntegration/TestConnectionBlocked.cs similarity index 96% rename from projects/Unit/TestConnectionBlocked.cs rename to projects/Test/SequentialIntegration/TestConnectionBlocked.cs index 4d9986aad6..232a83b954 100644 --- a/projects/Unit/TestConnectionBlocked.cs +++ b/projects/Test/SequentialIntegration/TestConnectionBlocked.cs @@ -36,9 +36,9 @@ using Xunit; using Xunit.Abstractions; -namespace RabbitMQ.Client.Unit +namespace Test.SequentialIntegration { - public class TestConnectionBlocked : IntegrationFixture + public class TestConnectionBlocked : SequentialIntegrationFixture { private readonly ManualResetEventSlim _connDisposed = new ManualResetEventSlim(false); private readonly object _lockObject = new object(); @@ -100,7 +100,7 @@ public void TestDisposeOnBlockedConnectionDoesNotHang() } } - protected override void ReleaseResources() + protected override void TearDown() { Unblock(); } diff --git a/projects/Unit/TestConnectionRecovery.cs b/projects/Test/SequentialIntegration/TestConnectionRecovery.cs similarity index 67% rename from projects/Unit/TestConnectionRecovery.cs rename to projects/Test/SequentialIntegration/TestConnectionRecovery.cs index 2c8c42ab50..fada4e8948 100644 --- a/projects/Unit/TestConnectionRecovery.cs +++ b/projects/Test/SequentialIntegration/TestConnectionRecovery.cs @@ -31,9 +31,9 @@ using System; using System.Collections.Generic; -using System.Text; using System.Threading; using System.Threading.Tasks; +using RabbitMQ.Client; using RabbitMQ.Client.Events; using RabbitMQ.Client.Exceptions; using RabbitMQ.Client.Framing.Impl; @@ -41,46 +41,25 @@ using Xunit; using Xunit.Abstractions; -#pragma warning disable 0618 - -namespace RabbitMQ.Client.Unit +namespace Test.SequentialIntegration { - public class TestConnectionRecovery : IntegrationFixture + public class TestConnectionRecovery : TestConnectionRecoveryBase { - private readonly byte[] _messageBody; - private readonly ushort _totalMessageCount = 8192; - private readonly ushort _closeAtCount = 16; - private string _queueName; + private readonly string _queueName; public TestConnectionRecovery(ITestOutputHelper output) : base(output) - { - var rnd = new Random(); - _messageBody = new byte[4096]; - rnd.NextBytes(_messageBody); - } - - protected override void SetUp() { _queueName = $"TestConnectionRecovery-queue-{Guid.NewGuid()}"; - _conn = CreateAutorecoveringConnection(); - _channel = _conn.CreateChannel(); - _channel.QueueDelete(_queueName); } - protected override void ReleaseResources() + protected override void TearDown() { - // TODO LRB not really necessary - if (_channel.IsOpen) - { - _channel.Close(); - } - - if (_conn.IsOpen) - { - _conn.Close(); - } - - Unblock(); + var cf = CreateConnectionFactory(); + cf.ClientProvidedName = cf.ClientProvidedName + "-TearDown"; + using IConnection conn = cf.CreateConnection(); + using IChannel ch = conn.CreateChannel(); + ch.QueueDelete(_queueName); + base.TearDown(); } [Fact] @@ -93,16 +72,16 @@ public void TestBasicAckAfterChannelRecovery() Assert.Equal(queueName, _queueName); _channel.BasicQos(0, 1, false); - string consumerTag = _channel.BasicConsume(queueName, false, cons); + _channel.BasicConsume(queueName, false, cons); ManualResetEventSlim sl = PrepareForShutdown(_conn); ManualResetEventSlim rl = PrepareForRecovery(_conn); PublishMessagesWhileClosingConn(queueName); - Wait(sl); - Wait(rl); - Wait(allMessagesSeenLatch); + Wait(sl, "connection shutdown"); + Wait(rl, "connection recovery"); + Wait(allMessagesSeenLatch, "all messages seen"); } [Fact] @@ -115,16 +94,16 @@ public void TestBasicNackAfterChannelRecovery() Assert.Equal(queueName, _queueName); _channel.BasicQos(0, 1, false); - string consumerTag = _channel.BasicConsume(queueName, false, cons); + _channel.BasicConsume(queueName, false, cons); ManualResetEventSlim sl = PrepareForShutdown(_conn); ManualResetEventSlim rl = PrepareForRecovery(_conn); PublishMessagesWhileClosingConn(queueName); - Wait(sl); - Wait(rl); - Wait(allMessagesSeenLatch); + Wait(sl, "connection shutdown"); + Wait(rl, "connection recovery"); + Wait(allMessagesSeenLatch, "all messages seen"); } [Fact] @@ -137,16 +116,16 @@ public void TestBasicRejectAfterChannelRecovery() Assert.Equal(queueName, _queueName); _channel.BasicQos(0, 1, false); - string consumerTag = _channel.BasicConsume(queueName, false, cons); + _channel.BasicConsume(queueName, false, cons); ManualResetEventSlim sl = PrepareForShutdown(_conn); ManualResetEventSlim rl = PrepareForRecovery(_conn); PublishMessagesWhileClosingConn(queueName); - Wait(sl); - Wait(rl); - Wait(allMessagesSeenLatch); + Wait(sl, "connection shutdown"); + Wait(rl, "connection recovery"); + Wait(allMessagesSeenLatch, "all messages seen"); } [Fact] @@ -180,7 +159,7 @@ public void TestBasicAckEventHandlerRecovery() Assert.True(_channel.IsOpen); WithTemporaryNonExclusiveQueue(_channel, (m, q) => m.BasicPublish("", q, _messageBody)); - Wait(latch); + Wait(latch, "basic acks/nacks"); } [Fact] @@ -191,86 +170,6 @@ public void TestBasicConnectionRecovery() Assert.True(_conn.IsOpen); } - [Fact] - public void TestBasicConnectionRecoveryWithHostnameList() - { - using (AutorecoveringConnection c = CreateAutorecoveringConnection(new List { "127.0.0.1", "localhost" })) - { - Assert.True(c.IsOpen); - CloseAndWaitForRecovery(c); - Assert.True(c.IsOpen); - } - } - - [Fact] - public void TestBasicConnectionRecoveryWithHostnameListAndUnreachableHosts() - { - using (AutorecoveringConnection c = CreateAutorecoveringConnection(new List { "191.72.44.22", "127.0.0.1", "localhost" })) - { - Assert.True(c.IsOpen); - CloseAndWaitForRecovery(c); - Assert.True(c.IsOpen); - } - } - - [Fact] - public void TestBasicConnectionRecoveryWithEndpointList() - { - using (AutorecoveringConnection c = CreateAutorecoveringConnection( - new List - { - new AmqpTcpEndpoint("127.0.0.1"), - new AmqpTcpEndpoint("localhost") - })) - { - Assert.True(c.IsOpen); - CloseAndWaitForRecovery(c); - Assert.True(c.IsOpen); - } - } - - [Fact] - public void TestBasicConnectionRecoveryStopsAfterManualClose() - { - Assert.True(_conn.IsOpen); - AutorecoveringConnection c = CreateAutorecoveringConnection(); - var latch = new AutoResetEvent(false); - c.ConnectionRecoveryError += (o, args) => latch.Set(); - - try - { - StopRabbitMQ(); - latch.WaitOne(30000); // we got the failed reconnection event. - bool triedRecoveryAfterClose = false; - c.Close(); - Thread.Sleep(5000); - c.ConnectionRecoveryError += (o, args) => triedRecoveryAfterClose = true; - Thread.Sleep(10000); - Assert.False(triedRecoveryAfterClose); - } - finally - { - StartRabbitMQ(); - } - } - - [Fact] - public void TestBasicConnectionRecoveryWithEndpointListAndUnreachableHosts() - { - using (AutorecoveringConnection c = CreateAutorecoveringConnection( - new List - { - new AmqpTcpEndpoint("191.72.44.22"), - new AmqpTcpEndpoint("127.0.0.1"), - new AmqpTcpEndpoint("localhost") - })) - { - Assert.True(c.IsOpen); - CloseAndWaitForRecovery(c); - Assert.True(c.IsOpen); - } - } - [Fact] public void TestBasicConnectionRecoveryOnBrokerRestart() { @@ -304,7 +203,7 @@ public void TestBlockedListenersRecovery() CloseAndWaitForRecovery(); Block(); - Wait(latch); + Wait(latch, "connection blocked"); Unblock(); } @@ -344,104 +243,6 @@ public void TestClientNamedQueueRecoveryOnServerRestart() }, s); } - [Fact] - public void TestConsumerWorkServiceRecovery() - { - using (AutorecoveringConnection c = CreateAutorecoveringConnection()) - { - IChannel m = c.CreateChannel(); - string q = m.QueueDeclare("dotnet-client.recovery.consumer_work_pool1", - false, false, false, null).QueueName; - var cons = new EventingBasicConsumer(m); - m.BasicConsume(q, true, cons); - AssertConsumerCount(m, q, 1); - - CloseAndWaitForRecovery(c); - - Assert.True(m.IsOpen); - var latch = new ManualResetEventSlim(false); - cons.Received += (s, args) => latch.Set(); - - m.BasicPublish("", q, _encoding.GetBytes("msg")); - Wait(latch); - - m.QueueDelete(q); - } - } - - [Fact] - public void TestConsumerRecoveryOnClientNamedQueueWithOneRecovery() - { - string q0 = "dotnet-client.recovery.queue1"; - using (AutorecoveringConnection c = CreateAutorecoveringConnection()) - { - IChannel m = c.CreateChannel(); - string q1 = m.QueueDeclare(q0, false, false, false, null).QueueName; - Assert.Equal(q0, q1); - - var cons = new EventingBasicConsumer(m); - m.BasicConsume(q1, true, cons); - AssertConsumerCount(m, q1, 1); - - bool queueNameChangeAfterRecoveryCalled = false; - - c.QueueNameChangeAfterRecovery += (source, ea) => { queueNameChangeAfterRecoveryCalled = true; }; - - CloseAndWaitForRecovery(c); - AssertConsumerCount(m, q1, 1); - Assert.False(queueNameChangeAfterRecoveryCalled); - - CloseAndWaitForRecovery(c); - AssertConsumerCount(m, q1, 1); - Assert.False(queueNameChangeAfterRecoveryCalled); - - CloseAndWaitForRecovery(c); - AssertConsumerCount(m, q1, 1); - Assert.False(queueNameChangeAfterRecoveryCalled); - - var latch = new ManualResetEventSlim(false); - cons.Received += (s, args) => latch.Set(); - - m.BasicPublish("", q1, _encoding.GetBytes("msg")); - Wait(latch); - - m.QueueDelete(q1); - } - } - - [Fact] - public void TestConsumerRecoveryWithServerNamedQueue() - { - // https://github.com/rabbitmq/rabbitmq-dotnet-client/issues/1238 - using (AutorecoveringConnection c = CreateAutorecoveringConnection()) - { - IChannel ch = c.CreateChannel(); - QueueDeclareOk queueDeclareResult = ch.QueueDeclare(queue: string.Empty, durable: false, exclusive: true, autoDelete: true, arguments: null); - string qname = queueDeclareResult.QueueName; - Assert.False(string.IsNullOrEmpty(qname)); - - var cons = new EventingBasicConsumer(ch); - ch.BasicConsume(string.Empty, true, cons); - AssertConsumerCount(ch, qname, 1); - - bool queueNameBeforeIsEqual = false; - bool queueNameChangeAfterRecoveryCalled = false; - string qnameAfterRecovery = null; - c.QueueNameChangeAfterRecovery += (source, ea) => - { - queueNameChangeAfterRecoveryCalled = true; - queueNameBeforeIsEqual = qname.Equals(ea.NameBefore); - qnameAfterRecovery = ea.NameAfter; - }; - - CloseAndWaitForRecovery(c); - - AssertConsumerCount(ch, qnameAfterRecovery, 1); - Assert.True(queueNameChangeAfterRecoveryCalled); - Assert.True(queueNameBeforeIsEqual); - } - } - [Fact] public void TestConsumerRecoveryWithManyConsumers() { @@ -458,39 +259,11 @@ public void TestConsumerRecoveryWithManyConsumers() ((AutorecoveringConnection)_conn).ConsumerTagChangeAfterRecovery += (prev, current) => latch.Set(); CloseAndWaitForRecovery(); - Wait(latch); + Wait(latch, "consumer tag change after recovery"); Assert.True(_channel.IsOpen); AssertConsumerCount(q, n); } - [Fact] - public void TestCreateChannelOnClosedAutorecoveringConnectionDoesNotHang() - { - // we don't want this to recover quickly in this test - AutorecoveringConnection c = CreateAutorecoveringConnection(TimeSpan.FromSeconds(20)); - - try - { - c.Close(); - WaitForShutdown(c); - Assert.False(c.IsOpen); - c.CreateChannel(); - Assert.Fail("Expected an exception"); - } - catch (AlreadyClosedException) - { - // expected - } - finally - { - StartRabbitMQ(); - if (c.IsOpen) - { - c.Abort(); - } - } - } - [Fact] public void TestDeclarationOfManyAutoDeleteExchangesWithTransientExchangesThatAreDeleted() { @@ -532,7 +305,7 @@ public void TestDeclarationOfManyAutoDeleteExchangesWithTransientQueuesThatAreDe { string x = Guid.NewGuid().ToString(); _channel.ExchangeDeclare(x, "fanout", false, true, null); - QueueDeclareOk q = _channel.QueueDeclare(); + RabbitMQ.Client.QueueDeclareOk q = _channel.QueueDeclare(); _channel.QueueBind(q, x, ""); _channel.QueueDelete(q); } @@ -547,7 +320,7 @@ public void TestDeclarationOfManyAutoDeleteExchangesWithTransientQueuesThatAreUn { string x = Guid.NewGuid().ToString(); _channel.ExchangeDeclare(x, "fanout", false, true, null); - QueueDeclareOk q = _channel.QueueDeclare(); + RabbitMQ.Client.QueueDeclareOk q = _channel.QueueDeclare(); _channel.QueueBind(q, x, ""); _channel.QueueUnbind(q, x, "", null); } @@ -654,7 +427,7 @@ public void TestClientNamedTransientAutoDeleteQueueAndBindingRecovery() ch.ExchangeDeclare(exchange: x, type: "fanout"); ch.BasicPublish(exchange: x, routingKey: "", body: _encoding.GetBytes("msg")); WaitForConfirms(ch); - QueueDeclareOk ok = ch.QueueDeclare(queue: q, durable: false, exclusive: false, autoDelete: true, arguments: null); + RabbitMQ.Client.QueueDeclareOk ok = ch.QueueDeclare(queue: q, durable: false, exclusive: false, autoDelete: true, arguments: null); Assert.Equal(1u, ok.MessageCount); ch.QueueDelete(q); ch.ExchangeDelete(x); @@ -680,14 +453,14 @@ public void TestServerNamedTransientAutoDeleteQueueAndBindingRecovery() }; ch.QueueBind(queue: nameBefore, exchange: x, routingKey: ""); RestartServerAndWaitForRecovery(); - Wait(latch); + Wait(latch, "queue name change after recovery"); Assert.True(ch.IsOpen); Assert.NotEqual(nameBefore, nameAfter); ch.ConfirmSelect(); ch.ExchangeDeclare(exchange: x, type: "fanout"); ch.BasicPublish(exchange: x, routingKey: "", body: _encoding.GetBytes("msg")); WaitForConfirms(ch); - QueueDeclareOk ok = ch.QueueDeclarePassive(nameAfter); + RabbitMQ.Client.QueueDeclareOk ok = ch.QueueDeclarePassive(nameAfter); Assert.Equal(1u, ok.MessageCount); ch.QueueDelete(q); ch.ExchangeDelete(x); @@ -768,34 +541,6 @@ public void TestRecoveringConsumerHandlerOnConnection_EventArgumentsArePassedDow Assert.Equal("event-handler-set-this-value", actualVal); } - [Fact] - public void TestRecoveryWithTopologyDisabled() - { - AutorecoveringConnection conn = CreateAutorecoveringConnectionWithTopologyRecoveryDisabled(); - IChannel ch = conn.CreateChannel(); - string s = "dotnet-client.test.recovery.q2"; - ch.QueueDelete(s); - ch.QueueDeclare(s, false, true, false, null); - ch.QueueDeclarePassive(s); - Assert.True(ch.IsOpen); - - try - { - CloseAndWaitForRecovery(conn); - Assert.True(ch.IsOpen); - ch.QueueDeclarePassive(s); - Assert.Fail("Expected an exception"); - } - catch (OperationInterruptedException) - { - // expected - } - finally - { - conn.Abort(); - } - } - [Fact] public void TestServerNamedQueueRecovery() { @@ -812,7 +557,7 @@ public void TestServerNamedQueueRecovery() connection.QueueNameChangeAfterRecovery += (source, ea) => { nameAfter = ea.NameAfter; }; CloseAndWaitForRecovery(); - Wait(latch); + Wait(latch, "recovery succeeded"); Assert.NotNull(nameAfter); Assert.StartsWith("amq.", nameBefore); @@ -851,7 +596,6 @@ public void TestShutdownEventHandlersRecoveryOnConnectionAfterDelayedServerResta try { StopRabbitMQ(); - Console.WriteLine("Stopped RabbitMQ. About to sleep for multiple recovery intervals..."); Thread.Sleep(7000); } finally @@ -859,8 +603,8 @@ public void TestShutdownEventHandlersRecoveryOnConnectionAfterDelayedServerResta StartRabbitMQ(); } - Wait(shutdownLatch, TimeSpan.FromSeconds(30)); - Wait(recoveryLatch, TimeSpan.FromSeconds(30)); + Wait(shutdownLatch, WaitSpan, "connection shutdown"); + Wait(recoveryLatch, WaitSpan, "connection recovery"); Assert.True(_conn.IsOpen); Assert.True(counter >= 1); } @@ -906,7 +650,7 @@ public void TestRecoverTopologyOnDisposedChannel() cons.Received += (s, args) => latch.Set(); _channel.BasicPublish("", q, _messageBody); - Wait(latch); + Wait(latch, "received event"); _channel.QueueUnbind(q, x, rk); _channel.ExchangeDelete(x); @@ -1083,7 +827,7 @@ public void TestUnblockedListenersRecovery() Block(); Unblock(); - Wait(latch); + Wait(latch, "connection unblocked"); } [Fact] @@ -1109,7 +853,7 @@ public void TestTopologyRecoveryQueueFilter() try { CloseAndWaitForRecovery(conn); - Wait(latch); + Wait(latch, "recovery succeeded"); Assert.True(ch.IsOpen); AssertQueueRecovery(ch, queueToRecover, false); @@ -1153,7 +897,7 @@ public void TestTopologyRecoveryExchangeFilter() try { CloseAndWaitForRecovery(conn); - Wait(latch); + Wait(latch, "recovery succeeded"); Assert.True(ch.IsOpen); AssertExchangeRecovery(ch, exchangeToRecover); @@ -1206,78 +950,11 @@ public void TestTopologyRecoveryBindingFilter() try { CloseAndWaitForRecovery(conn); - Wait(latch); - - Assert.True(ch.IsOpen); - Assert.True(SendAndConsumeMessage(queueWithRecoveredBinding, exchange, bindingToRecover)); - Assert.False(SendAndConsumeMessage(queueWithIgnoredBinding, exchange, bindingToIgnore)); - } - finally - { - conn.Abort(); - } - } - - [Fact] - public void TestTopologyRecoveryConsumerFilter() - { - var filter = new TopologyRecoveryFilter - { - ConsumerFilter = consumer => !consumer.ConsumerTag.Contains("filtered") - }; - var latch = new ManualResetEventSlim(false); - AutorecoveringConnection conn = CreateAutorecoveringConnectionWithTopologyRecoveryFilter(filter); - conn.RecoverySucceeded += (source, ea) => latch.Set(); - IChannel ch = conn.CreateChannel(); - ch.ConfirmSelect(); - - var exchange = "topology.recovery.exchange"; - var queueWithRecoveredConsumer = "topology.recovery.queue.1"; - var queueWithIgnoredConsumer = "topology.recovery.queue.2"; - var binding1 = "recovered.binding.1"; - var binding2 = "recovered.binding.2"; - - ch.ExchangeDeclare(exchange, "direct"); - ch.QueueDeclare(queueWithRecoveredConsumer, false, false, false, null); - ch.QueueDeclare(queueWithIgnoredConsumer, false, false, false, null); - ch.QueueBind(queueWithRecoveredConsumer, exchange, binding1); - ch.QueueBind(queueWithIgnoredConsumer, exchange, binding2); - ch.QueuePurge(queueWithRecoveredConsumer); - ch.QueuePurge(queueWithIgnoredConsumer); - - var recoverLatch = new ManualResetEventSlim(false); - var consumerToRecover = new EventingBasicConsumer(ch); - consumerToRecover.Received += (source, ea) => recoverLatch.Set(); - ch.BasicConsume(queueWithRecoveredConsumer, true, "recovered.consumer", consumerToRecover); - - var ignoredLatch = new ManualResetEventSlim(false); - var consumerToIgnore = new EventingBasicConsumer(ch); - consumerToIgnore.Received += (source, ea) => ignoredLatch.Set(); - ch.BasicConsume(queueWithIgnoredConsumer, true, "filtered.consumer", consumerToIgnore); - - try - { - CloseAndWaitForRecovery(conn); - Wait(latch); + Wait(latch, "recovery succeeded"); Assert.True(ch.IsOpen); - ch.BasicPublish(exchange, binding1, Encoding.UTF8.GetBytes("test message")); - ch.BasicPublish(exchange, binding2, Encoding.UTF8.GetBytes("test message")); - - Assert.True(recoverLatch.Wait(TimeSpan.FromSeconds(5))); - Assert.False(ignoredLatch.Wait(TimeSpan.FromSeconds(5))); - - ch.BasicConsume(queueWithIgnoredConsumer, true, "filtered.consumer", consumerToIgnore); - - try - { - ch.BasicConsume(queueWithRecoveredConsumer, true, "recovered.consumer", consumerToRecover); - Assert.Fail("Expected an exception"); - } - catch (OperationInterruptedException e) - { - AssertShutdownError(e.ShutdownReason, 530); // NOT_ALLOWED - not allowed to reuse consumer tag - } + Assert.True(SendAndConsumeMessage(_conn, queueWithRecoveredBinding, exchange, bindingToRecover)); + Assert.False(SendAndConsumeMessage(_conn, queueWithIgnoredBinding, exchange, bindingToIgnore)); } finally { @@ -1326,15 +1003,15 @@ public void TestTopologyRecoveryDefaultFilterRecoversAllEntities() try { CloseAndWaitForRecovery(conn); - Wait(latch); + Wait(latch, "recovery succeeded"); Assert.True(ch.IsOpen); AssertExchangeRecovery(ch, exchange); ch.QueueDeclarePassive(queue1); ch.QueueDeclarePassive(queue2); - ch.BasicPublish(exchange, binding1, Encoding.UTF8.GetBytes("test message")); - ch.BasicPublish(exchange, binding2, Encoding.UTF8.GetBytes("test message")); + ch.BasicPublish(exchange, binding1, _encoding.GetBytes("test message")); + ch.BasicPublish(exchange, binding2, _encoding.GetBytes("test message")); Assert.True(consumerLatch1.Wait(TimeSpan.FromSeconds(5))); Assert.True(consumerLatch2.Wait(TimeSpan.FromSeconds(5))); @@ -1385,7 +1062,7 @@ public void TestTopologyRecoveryQueueExceptionHandler() try { CloseAndWaitForRecovery(conn); - Wait(latch); + Wait(latch, "recovery succeded"); Assert.True(ch.IsOpen); AssertQueueRecovery(ch, queueToRecoverSuccessfully, false); @@ -1436,7 +1113,7 @@ public void TestTopologyRecoveryExchangeExceptionHandler() try { CloseAndWaitForRecovery(conn); - Wait(latch); + Wait(latch, "recovery succeeded"); Assert.True(ch.IsOpen); AssertExchangeRecovery(ch, exchangeToRecoverSuccessfully); @@ -1499,11 +1176,11 @@ public void TestTopologyRecoveryBindingExceptionHandler() try { CloseAndWaitForRecovery(conn); - Wait(latch); + Wait(latch, "recovery succeeded"); Assert.True(ch.IsOpen); - Assert.True(SendAndConsumeMessage(queueWithRecoveredBinding, exchange, bindingToRecoverSuccessfully)); - Assert.True(SendAndConsumeMessage(queueWithExceptionBinding, exchange, bindingToRecoverWithException)); + Assert.True(SendAndConsumeMessage(conn, queueWithRecoveredBinding, exchange, bindingToRecoverSuccessfully)); + Assert.True(SendAndConsumeMessage(conn, queueWithExceptionBinding, exchange, bindingToRecoverWithException)); } finally { @@ -1555,11 +1232,11 @@ public void TestTopologyRecoveryConsumerExceptionHandler() try { CloseAndWaitForShutdown(conn); - Wait(latch, TimeSpan.FromSeconds(20)); + Wait(latch, TimeSpan.FromSeconds(20), "recovery succeeded"); Assert.True(ch.IsOpen); - ch.BasicPublish("", queueWithExceptionConsumer, Encoding.UTF8.GetBytes("test message")); + ch.BasicPublish("", queueWithExceptionConsumer, _encoding.GetBytes("test message")); Assert.True(recoverLatch.Wait(TimeSpan.FromSeconds(5))); @@ -1578,236 +1255,5 @@ public void TestTopologyRecoveryConsumerExceptionHandler() conn.Abort(); } } - - internal bool SendAndConsumeMessage(string queue, string exchange, string routingKey) - { - using (var ch = _conn.CreateChannel()) - { - var latch = new ManualResetEventSlim(false); - - var consumer = new AckingBasicConsumer(ch, 1, latch); - - ch.BasicConsume(queue, false, consumer); - - ch.BasicPublish(exchange, routingKey, Encoding.UTF8.GetBytes("test message")); - - return latch.Wait(TimeSpan.FromSeconds(5)); - } - } - - internal void AssertExchangeRecovery(IChannel m, string x) - { - m.ConfirmSelect(); - WithTemporaryNonExclusiveQueue(m, (_, q) => - { - string rk = "routing-key"; - m.QueueBind(q, x, rk); - m.BasicPublish(x, rk, _messageBody); - - Assert.True(WaitForConfirms(m)); - m.ExchangeDeclarePassive(x); - }); - } - - internal void AssertQueueRecovery(IChannel m, string q) - { - AssertQueueRecovery(m, q, true); - } - - internal void AssertQueueRecovery(IChannel m, string q, bool exclusive, IDictionary arguments = null) - { - m.ConfirmSelect(); - m.QueueDeclarePassive(q); - QueueDeclareOk ok1 = m.QueueDeclare(q, false, exclusive, false, arguments); - Assert.Equal(0u, ok1.MessageCount); - m.BasicPublish("", q, _messageBody); - Assert.True(WaitForConfirms(m)); - QueueDeclareOk ok2 = m.QueueDeclare(q, false, exclusive, false, arguments); - Assert.Equal(1u, ok2.MessageCount); - } - - internal void AssertRecordedExchanges(AutorecoveringConnection c, int n) - { - Assert.Equal(n, c.RecordedExchangesCount); - } - - internal void AssertRecordedQueues(AutorecoveringConnection c, int n) - { - Assert.Equal(n, c.RecordedQueuesCount); - } - - internal void CloseAndWaitForRecovery() - { - CloseAndWaitForRecovery((AutorecoveringConnection)_conn); - } - - internal void CloseAndWaitForRecovery(AutorecoveringConnection conn) - { - ManualResetEventSlim sl = PrepareForShutdown(conn); - ManualResetEventSlim rl = PrepareForRecovery(conn); - CloseConnection(conn); - Wait(sl); - Wait(rl); - } - - internal void CloseAndWaitForShutdown(AutorecoveringConnection conn) - { - ManualResetEventSlim sl = PrepareForShutdown(conn); - CloseConnection(conn); - Wait(sl); - } - - internal ManualResetEventSlim PrepareForRecovery(IConnection conn) - { - var latch = new ManualResetEventSlim(false); - - AutorecoveringConnection aconn = conn as AutorecoveringConnection; - aconn.RecoverySucceeded += (source, ea) => latch.Set(); - - return latch; - } - - internal static ManualResetEventSlim PrepareForShutdown(IConnection conn) - { - var latch = new ManualResetEventSlim(false); - - AutorecoveringConnection aconn = conn as AutorecoveringConnection; - aconn.ConnectionShutdown += (c, args) => latch.Set(); - - return latch; - } - - internal void RestartServerAndWaitForRecovery() - { - RestartServerAndWaitForRecovery((AutorecoveringConnection)_conn); - } - - internal void RestartServerAndWaitForRecovery(AutorecoveringConnection conn) - { - ManualResetEventSlim sl = PrepareForShutdown(conn); - ManualResetEventSlim rl = PrepareForRecovery(conn); - RestartRabbitMQ(); - Wait(sl); - Wait(rl); - } - - internal void WaitForRecovery() - { - Wait(PrepareForRecovery((AutorecoveringConnection)_conn)); - } - - internal void WaitForRecovery(AutorecoveringConnection conn) - { - Wait(PrepareForRecovery(conn)); - } - - internal void WaitForShutdown() - { - Wait(PrepareForShutdown(_conn)); - } - - internal void WaitForShutdown(IConnection conn) - { - Wait(PrepareForShutdown(conn)); - } - - internal void PublishMessagesWhileClosingConn(string queueName) - { - using (AutorecoveringConnection publishingConn = CreateAutorecoveringConnection()) - { - using (IChannel publishingChannel = publishingConn.CreateChannel()) - { - for (ushort i = 0; i < _totalMessageCount; i++) - { - if (i == _closeAtCount) - { - CloseConnection(_conn); - } - publishingChannel.BasicPublish(string.Empty, queueName, _messageBody); - } - } - } - } - - public class AckingBasicConsumer : TestBasicConsumer - { - public AckingBasicConsumer(IChannel channel, ushort totalMessageCount, ManualResetEventSlim allMessagesSeenLatch) - : base(channel, totalMessageCount, allMessagesSeenLatch) - { - } - - public override void PostHandleDelivery(ulong deliveryTag) - { - Channel.BasicAck(deliveryTag, false); - } - } - - public class NackingBasicConsumer : TestBasicConsumer - { - public NackingBasicConsumer(IChannel channel, ushort totalMessageCount, ManualResetEventSlim allMessagesSeenLatch) - : base(channel, totalMessageCount, allMessagesSeenLatch) - { - } - - public override void PostHandleDelivery(ulong deliveryTag) - { - Channel.BasicNack(deliveryTag, false, false); - } - } - - public class RejectingBasicConsumer : TestBasicConsumer - { - public RejectingBasicConsumer(IChannel channel, ushort totalMessageCount, ManualResetEventSlim allMessagesSeenLatch) - : base(channel, totalMessageCount, allMessagesSeenLatch) - { - } - - public override void PostHandleDelivery(ulong deliveryTag) - { - Channel.BasicReject(deliveryTag, false); - } - } - - public class TestBasicConsumer : DefaultBasicConsumer - { - private readonly ManualResetEventSlim _allMessagesSeenLatch; - private readonly ushort _totalMessageCount; - private ushort _counter = 0; - - public TestBasicConsumer(IChannel channel, ushort totalMessageCount, ManualResetEventSlim allMessagesSeenLatch) - : base(channel) - { - _totalMessageCount = totalMessageCount; - _allMessagesSeenLatch = allMessagesSeenLatch; - } - - public override void HandleBasicDeliver(string consumerTag, - ulong deliveryTag, - bool redelivered, - string exchange, - string routingKey, - in ReadOnlyBasicProperties properties, - ReadOnlyMemory body) - { - try - { - PostHandleDelivery(deliveryTag); - } - finally - { - ++_counter; - if (_counter >= _totalMessageCount) - { - _allMessagesSeenLatch.Set(); - } - } - } - - public virtual void PostHandleDelivery(ulong deliveryTag) - { - } - } } } - -#pragma warning restore 0168 diff --git a/projects/Test/SequentialIntegration/TestConnectionRecoveryBase.cs b/projects/Test/SequentialIntegration/TestConnectionRecoveryBase.cs new file mode 100644 index 0000000000..773a19a8b8 --- /dev/null +++ b/projects/Test/SequentialIntegration/TestConnectionRecoveryBase.cs @@ -0,0 +1,389 @@ +// This source code is dual-licensed under the Apache License, version +// 2.0, and the Mozilla Public License, version 2.0. +// +// The APL v2.0: +// +//--------------------------------------------------------------------------- +// Copyright (c) 2007-2020 VMware, Inc. +// +// 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 +// +// https://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. +//--------------------------------------------------------------------------- +// +// The MPL v2.0: +// +//--------------------------------------------------------------------------- +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2007-2020 VMware, Inc. All rights reserved. +//--------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Threading; +using RabbitMQ.Client; +using RabbitMQ.Client.Framing.Impl; +using Xunit; +using Xunit.Abstractions; + +namespace Test.SequentialIntegration +{ + public class TestConnectionRecoveryBase : SequentialIntegrationFixture + { + protected readonly byte[] _messageBody; + protected const ushort _totalMessageCount = 8192; + protected const ushort _closeAtCount = 16; + + public TestConnectionRecoveryBase(ITestOutputHelper output) : base(output) + { + _messageBody = GetRandomBody(4096); + } + + protected override void TearDown() + { + Unblock(); + } + + protected void AssertConsumerCount(string q, int count) + { + WithTemporaryChannel((m) => + { + RabbitMQ.Client.QueueDeclareOk ok = m.QueueDeclarePassive(q); + Assert.Equal((uint)count, ok.ConsumerCount); + }); + } + + protected void AssertConsumerCount(IChannel m, string q, uint count) + { + RabbitMQ.Client.QueueDeclareOk ok = m.QueueDeclarePassive(q); + Assert.Equal(count, ok.ConsumerCount); + } + + protected void AssertExchangeRecovery(IChannel m, string x) + { + m.ConfirmSelect(); + WithTemporaryNonExclusiveQueue(m, (_, q) => + { + string rk = "routing-key"; + m.QueueBind(q, x, rk); + m.BasicPublish(x, rk, _messageBody); + + Assert.True(WaitForConfirms(m)); + m.ExchangeDeclarePassive(x); + }); + } + + protected void AssertQueueRecovery(IChannel m, string q) + { + AssertQueueRecovery(m, q, true); + } + + protected void AssertQueueRecovery(IChannel m, string q, bool exclusive, IDictionary arguments = null) + { + m.ConfirmSelect(); + m.QueueDeclarePassive(q); + RabbitMQ.Client.QueueDeclareOk ok1 = m.QueueDeclare(q, false, exclusive, false, arguments); + Assert.Equal(0u, ok1.MessageCount); + m.BasicPublish("", q, _messageBody); + Assert.True(WaitForConfirms(m)); + RabbitMQ.Client.QueueDeclareOk ok2 = m.QueueDeclare(q, false, exclusive, false, arguments); + Assert.Equal(1u, ok2.MessageCount); + } + + internal void AssertRecordedExchanges(AutorecoveringConnection c, int n) + { + Assert.Equal(n, c.RecordedExchangesCount); + } + + internal void AssertRecordedQueues(AutorecoveringConnection c, int n) + { + Assert.Equal(n, c.RecordedQueuesCount); + } + + internal AutorecoveringConnection CreateAutorecoveringConnection() + { + return CreateAutorecoveringConnection(RecoveryInterval); + } + + internal AutorecoveringConnection CreateAutorecoveringConnection(TimeSpan networkRecoveryInterval) + { + var cf = CreateConnectionFactory(); + cf.AutomaticRecoveryEnabled = true; + cf.NetworkRecoveryInterval = networkRecoveryInterval; + return (AutorecoveringConnection)cf.CreateConnection(); + } + + internal AutorecoveringConnection CreateAutorecoveringConnection(IList endpoints) + { + var cf = CreateConnectionFactory(); + cf.AutomaticRecoveryEnabled = true; + // tests that use this helper will likely list unreachable hosts, + // make sure we time out quickly on those + cf.RequestedConnectionTimeout = TimeSpan.FromSeconds(1); + cf.NetworkRecoveryInterval = RecoveryInterval; + return (AutorecoveringConnection)cf.CreateConnection(endpoints); + } + + internal AutorecoveringConnection CreateAutorecoveringConnectionWithTopologyRecoveryDisabled() + { + var cf = CreateConnectionFactory(); + cf.AutomaticRecoveryEnabled = true; + cf.TopologyRecoveryEnabled = false; + cf.NetworkRecoveryInterval = RecoveryInterval; + return (AutorecoveringConnection)cf.CreateConnection(); + } + + internal AutorecoveringConnection CreateAutorecoveringConnectionWithTopologyRecoveryFilter(TopologyRecoveryFilter filter) + { + var cf = CreateConnectionFactory(); + cf.AutomaticRecoveryEnabled = true; + cf.TopologyRecoveryEnabled = true; + cf.TopologyRecoveryFilter = filter; + return (AutorecoveringConnection)cf.CreateConnection(); + } + + internal AutorecoveringConnection CreateAutorecoveringConnectionWithTopologyRecoveryExceptionHandler(TopologyRecoveryExceptionHandler handler) + { + var cf = CreateConnectionFactory(); + cf.AutomaticRecoveryEnabled = true; + cf.TopologyRecoveryEnabled = true; + cf.TopologyRecoveryExceptionHandler = handler; + return (AutorecoveringConnection)cf.CreateConnection(); + } + + protected void CloseConnection(IConnection conn) + { + _rabbitMQCtl.CloseConnection(conn); + } + + protected void CloseAndWaitForRecovery() + { + CloseAndWaitForRecovery((AutorecoveringConnection)_conn); + } + + internal void CloseAndWaitForRecovery(AutorecoveringConnection conn) + { + ManualResetEventSlim sl = PrepareForShutdown(conn); + ManualResetEventSlim rl = PrepareForRecovery(conn); + CloseConnection(conn); + Wait(sl, "connection shutdown"); + Wait(rl, "connection recovery"); + } + + internal void CloseAndWaitForShutdown(AutorecoveringConnection conn) + { + ManualResetEventSlim sl = PrepareForShutdown(conn); + CloseConnection(conn); + Wait(sl, "connection shutdown"); + } + + protected string DeclareNonDurableExchange(IChannel m, string x) + { + m.ExchangeDeclare(x, "fanout", false); + return x; + } + + protected string DeclareNonDurableExchangeNoWait(IChannel m, string x) + { + m.ExchangeDeclareNoWait(x, "fanout", false, false, null); + return x; + } + + protected ManualResetEventSlim PrepareForRecovery(IConnection conn) + { + var latch = new ManualResetEventSlim(false); + + AutorecoveringConnection aconn = conn as AutorecoveringConnection; + aconn.RecoverySucceeded += (source, ea) => latch.Set(); + + return latch; + } + + protected void PublishMessagesWhileClosingConn(string queueName) + { + using (AutorecoveringConnection publishingConn = CreateAutorecoveringConnection()) + { + using (IChannel publishingChannel = publishingConn.CreateChannel()) + { + for (ushort i = 0; i < _totalMessageCount; i++) + { + if (i == _closeAtCount) + { + CloseConnection(_conn); + } + publishingChannel.BasicPublish(string.Empty, queueName, _messageBody); + } + } + } + } + + protected static ManualResetEventSlim PrepareForShutdown(IConnection conn) + { + var latch = new ManualResetEventSlim(false); + + AutorecoveringConnection aconn = conn as AutorecoveringConnection; + aconn.ConnectionShutdown += (c, args) => latch.Set(); + + return latch; + } + + protected void RestartServerAndWaitForRecovery() + { + RestartServerAndWaitForRecovery((AutorecoveringConnection)_conn); + } + + internal void RestartServerAndWaitForRecovery(AutorecoveringConnection conn) + { + ManualResetEventSlim sl = PrepareForShutdown(conn); + ManualResetEventSlim rl = PrepareForRecovery(conn); + RestartRabbitMQ(); + Wait(sl, "connection shutdown"); + Wait(rl, "connection recovery"); + } + + protected bool WaitForConfirms(IChannel m) + { + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(4)); + return m.WaitForConfirmsAsync(cts.Token).GetAwaiter().GetResult(); + } + + protected void WaitForRecovery() + { + Wait(PrepareForRecovery((AutorecoveringConnection)_conn), "recovery succeded"); + } + + internal void WaitForRecovery(AutorecoveringConnection conn) + { + Wait(PrepareForRecovery(conn), "recovery succeeded"); + } + + protected void WaitForShutdown() + { + Wait(PrepareForShutdown(_conn), "connection shutdown"); + } + + protected void WaitForShutdown(IConnection conn) + { + Wait(PrepareForShutdown(conn), "connection shutdown"); + } + + protected void WithTemporaryQueueNoWait(IChannel channel, Action action, string queue) + { + try + { + channel.QueueDeclareNoWait(queue, false, true, false, null); + action(channel, queue); + } + finally + { + WithTemporaryChannel(x => x.QueueDelete(queue)); + } + } + + public class AckingBasicConsumer : TestBasicConsumer + { + public AckingBasicConsumer(IChannel channel, ushort totalMessageCount, ManualResetEventSlim allMessagesSeenLatch) + : base(channel, totalMessageCount, allMessagesSeenLatch) + { + } + + public override void PostHandleDelivery(ulong deliveryTag) + { + Channel.BasicAck(deliveryTag, false); + } + } + + public class NackingBasicConsumer : TestBasicConsumer + { + public NackingBasicConsumer(IChannel channel, ushort totalMessageCount, ManualResetEventSlim allMessagesSeenLatch) + : base(channel, totalMessageCount, allMessagesSeenLatch) + { + } + + public override void PostHandleDelivery(ulong deliveryTag) + { + Channel.BasicNack(deliveryTag, false, false); + } + } + + public class RejectingBasicConsumer : TestBasicConsumer + { + public RejectingBasicConsumer(IChannel channel, ushort totalMessageCount, ManualResetEventSlim allMessagesSeenLatch) + : base(channel, totalMessageCount, allMessagesSeenLatch) + { + } + + public override void PostHandleDelivery(ulong deliveryTag) + { + Channel.BasicReject(deliveryTag, false); + } + } + + public class TestBasicConsumer : DefaultBasicConsumer + { + protected readonly ManualResetEventSlim _allMessagesSeenLatch; + protected readonly ushort _totalMessageCount; + protected ushort _counter = 0; + + public TestBasicConsumer(IChannel channel, ushort totalMessageCount, ManualResetEventSlim allMessagesSeenLatch) + : base(channel) + { + _totalMessageCount = totalMessageCount; + _allMessagesSeenLatch = allMessagesSeenLatch; + } + + public override void HandleBasicDeliver(string consumerTag, + ulong deliveryTag, + bool redelivered, + string exchange, + string routingKey, + in ReadOnlyBasicProperties properties, + ReadOnlyMemory body) + { + try + { + PostHandleDelivery(deliveryTag); + } + finally + { + ++_counter; + if (_counter >= _totalMessageCount) + { + _allMessagesSeenLatch.Set(); + } + } + } + + public virtual void PostHandleDelivery(ulong deliveryTag) + { + } + } + + protected bool SendAndConsumeMessage(IConnection conn, string queue, string exchange, string routingKey) + { + using (IChannel ch = conn.CreateChannel()) + { + var latch = new ManualResetEventSlim(false); + + var consumer = new AckingBasicConsumer(ch, 1, latch); + + ch.BasicConsume(queue, false, consumer); + + ch.BasicPublish(exchange, routingKey, _encoding.GetBytes("test message")); + + return latch.Wait(TimeSpan.FromSeconds(5)); + } + } + } +} diff --git a/projects/Test/SequentialIntegration/TestConnectionRecoveryWithoutSetup.cs b/projects/Test/SequentialIntegration/TestConnectionRecoveryWithoutSetup.cs new file mode 100644 index 0000000000..8832c5e92e --- /dev/null +++ b/projects/Test/SequentialIntegration/TestConnectionRecoveryWithoutSetup.cs @@ -0,0 +1,333 @@ +// This source code is dual-licensed under the Apache License, version +// 2.0, and the Mozilla Public License, version 2.0. +// +// The APL v2.0: +// +//--------------------------------------------------------------------------- +// Copyright (c) 2007-2020 VMware, Inc. +// +// 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 +// +// https://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. +//--------------------------------------------------------------------------- +// +// The MPL v2.0: +// +//--------------------------------------------------------------------------- +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2007-2020 VMware, Inc. All rights reserved. +//--------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Threading; +using RabbitMQ.Client; +using RabbitMQ.Client.Events; +using RabbitMQ.Client.Exceptions; +using RabbitMQ.Client.Framing.Impl; +using Xunit; +using Xunit.Abstractions; + +namespace Test.SequentialIntegration +{ + public class TestConnectionRecoveryWithoutSetup : TestConnectionRecoveryBase + { + public TestConnectionRecoveryWithoutSetup(ITestOutputHelper output) : base(output) + { + } + + protected override void SetUp() + { + Assert.Null(_connFactory); + Assert.Null(_conn); + Assert.Null(_channel); + } + + [Fact] + public void TestBasicConnectionRecoveryWithHostnameList() + { + using (AutorecoveringConnection c = CreateAutorecoveringConnection(new List { "127.0.0.1", "localhost" })) + { + Assert.True(c.IsOpen); + CloseAndWaitForRecovery(c); + Assert.True(c.IsOpen); + } + } + + [Fact] + public void TestBasicConnectionRecoveryWithHostnameListAndUnreachableHosts() + { + using (AutorecoveringConnection c = CreateAutorecoveringConnection(new List { "191.72.44.22", "127.0.0.1", "localhost" })) + { + Assert.True(c.IsOpen); + CloseAndWaitForRecovery(c); + Assert.True(c.IsOpen); + } + } + + [Fact] + public void TestBasicConnectionRecoveryWithEndpointList() + { + using (AutorecoveringConnection c = CreateAutorecoveringConnection( + new List + { + new AmqpTcpEndpoint("127.0.0.1"), + new AmqpTcpEndpoint("localhost") + })) + { + Assert.True(c.IsOpen); + CloseAndWaitForRecovery(c); + Assert.True(c.IsOpen); + } + } + + [Fact] + public void TestBasicConnectionRecoveryWithEndpointListAndUnreachableHosts() + { + using (AutorecoveringConnection c = CreateAutorecoveringConnection( + new List + { + new AmqpTcpEndpoint("191.72.44.22"), + new AmqpTcpEndpoint("127.0.0.1"), + new AmqpTcpEndpoint("localhost") + })) + { + Assert.True(c.IsOpen); + CloseAndWaitForRecovery(c); + Assert.True(c.IsOpen); + } + } + + [Fact] + public void TestConsumerWorkServiceRecovery() + { + using (AutorecoveringConnection c = CreateAutorecoveringConnection()) + { + IChannel m = c.CreateChannel(); + string q = m.QueueDeclare("dotnet-client.recovery.consumer_work_pool1", + false, false, false, null).QueueName; + var cons = new EventingBasicConsumer(m); + m.BasicConsume(q, true, cons); + AssertConsumerCount(m, q, 1); + + CloseAndWaitForRecovery(c); + + Assert.True(m.IsOpen); + var latch = new ManualResetEventSlim(false); + cons.Received += (s, args) => latch.Set(); + + m.BasicPublish("", q, _encoding.GetBytes("msg")); + Wait(latch, "received event"); + + m.QueueDelete(q); + } + } + + [Fact] + public void TestConsumerRecoveryOnClientNamedQueueWithOneRecovery() + { + string q0 = "dotnet-client.recovery.queue1"; + using (AutorecoveringConnection c = CreateAutorecoveringConnection()) + { + IChannel m = c.CreateChannel(); + string q1 = m.QueueDeclare(q0, false, false, false, null).QueueName; + Assert.Equal(q0, q1); + + var cons = new EventingBasicConsumer(m); + m.BasicConsume(q1, true, cons); + AssertConsumerCount(m, q1, 1); + + bool queueNameChangeAfterRecoveryCalled = false; + + c.QueueNameChangeAfterRecovery += (source, ea) => { queueNameChangeAfterRecoveryCalled = true; }; + + CloseAndWaitForRecovery(c); + AssertConsumerCount(m, q1, 1); + Assert.False(queueNameChangeAfterRecoveryCalled); + + CloseAndWaitForRecovery(c); + AssertConsumerCount(m, q1, 1); + Assert.False(queueNameChangeAfterRecoveryCalled); + + CloseAndWaitForRecovery(c); + AssertConsumerCount(m, q1, 1); + Assert.False(queueNameChangeAfterRecoveryCalled); + + var latch = new ManualResetEventSlim(false); + cons.Received += (s, args) => latch.Set(); + + m.BasicPublish("", q1, _encoding.GetBytes("msg")); + Wait(latch, "received event"); + + m.QueueDelete(q1); + } + } + + [Fact] + public void TestConsumerRecoveryWithServerNamedQueue() + { + // https://github.com/rabbitmq/rabbitmq-dotnet-client/issues/1238 + using (AutorecoveringConnection c = CreateAutorecoveringConnection()) + { + IChannel ch = c.CreateChannel(); + RabbitMQ.Client.QueueDeclareOk queueDeclareResult = ch.QueueDeclare(queue: string.Empty, durable: false, exclusive: true, autoDelete: true, arguments: null); + string qname = queueDeclareResult.QueueName; + Assert.False(string.IsNullOrEmpty(qname)); + + var cons = new EventingBasicConsumer(ch); + ch.BasicConsume(string.Empty, true, cons); + AssertConsumerCount(ch, qname, 1); + + bool queueNameBeforeIsEqual = false; + bool queueNameChangeAfterRecoveryCalled = false; + string qnameAfterRecovery = null; + c.QueueNameChangeAfterRecovery += (source, ea) => + { + queueNameChangeAfterRecoveryCalled = true; + queueNameBeforeIsEqual = qname.Equals(ea.NameBefore); + qnameAfterRecovery = ea.NameAfter; + }; + + CloseAndWaitForRecovery(c); + + AssertConsumerCount(ch, qnameAfterRecovery, 1); + Assert.True(queueNameChangeAfterRecoveryCalled); + Assert.True(queueNameBeforeIsEqual); + } + } + + [Fact] + public void TestCreateChannelOnClosedAutorecoveringConnectionDoesNotHang() + { + // we don't want this to recover quickly in this test + AutorecoveringConnection c = CreateAutorecoveringConnection(TimeSpan.FromSeconds(20)); + + try + { + c.Close(); + WaitForShutdown(c); + Assert.False(c.IsOpen); + c.CreateChannel(); + Assert.Fail("Expected an exception"); + } + catch (AlreadyClosedException) + { + // expected + } + finally + { + StartRabbitMQ(); + if (c.IsOpen) + { + c.Abort(); + } + } + } + + [Fact] + public void TestTopologyRecoveryConsumerFilter() + { + var filter = new TopologyRecoveryFilter + { + ConsumerFilter = consumer => !consumer.ConsumerTag.Contains("filtered") + }; + var latch = new ManualResetEventSlim(false); + AutorecoveringConnection conn = CreateAutorecoveringConnectionWithTopologyRecoveryFilter(filter); + conn.RecoverySucceeded += (source, ea) => latch.Set(); + IChannel ch = conn.CreateChannel(); + ch.ConfirmSelect(); + + var exchange = "topology.recovery.exchange"; + var queueWithRecoveredConsumer = "topology.recovery.queue.1"; + var queueWithIgnoredConsumer = "topology.recovery.queue.2"; + var binding1 = "recovered.binding.1"; + var binding2 = "recovered.binding.2"; + + ch.ExchangeDeclare(exchange, "direct"); + ch.QueueDeclare(queueWithRecoveredConsumer, false, false, false, null); + ch.QueueDeclare(queueWithIgnoredConsumer, false, false, false, null); + ch.QueueBind(queueWithRecoveredConsumer, exchange, binding1); + ch.QueueBind(queueWithIgnoredConsumer, exchange, binding2); + ch.QueuePurge(queueWithRecoveredConsumer); + ch.QueuePurge(queueWithIgnoredConsumer); + + var recoverLatch = new ManualResetEventSlim(false); + var consumerToRecover = new EventingBasicConsumer(ch); + consumerToRecover.Received += (source, ea) => recoverLatch.Set(); + ch.BasicConsume(queueWithRecoveredConsumer, true, "recovered.consumer", consumerToRecover); + + var ignoredLatch = new ManualResetEventSlim(false); + var consumerToIgnore = new EventingBasicConsumer(ch); + consumerToIgnore.Received += (source, ea) => ignoredLatch.Set(); + ch.BasicConsume(queueWithIgnoredConsumer, true, "filtered.consumer", consumerToIgnore); + + try + { + CloseAndWaitForRecovery(conn); + Wait(latch, "recovery succeeded"); + + Assert.True(ch.IsOpen); + ch.BasicPublish(exchange, binding1, _encoding.GetBytes("test message")); + ch.BasicPublish(exchange, binding2, _encoding.GetBytes("test message")); + + Assert.True(recoverLatch.Wait(TimeSpan.FromSeconds(5))); + Assert.False(ignoredLatch.Wait(TimeSpan.FromSeconds(5))); + + ch.BasicConsume(queueWithIgnoredConsumer, true, "filtered.consumer", consumerToIgnore); + + try + { + ch.BasicConsume(queueWithRecoveredConsumer, true, "recovered.consumer", consumerToRecover); + Assert.Fail("Expected an exception"); + } + catch (OperationInterruptedException e) + { + AssertShutdownError(e.ShutdownReason, 530); // NOT_ALLOWED - not allowed to reuse consumer tag + } + } + finally + { + conn.Abort(); + } + } + + [Fact] + public void TestRecoveryWithTopologyDisabled() + { + AutorecoveringConnection conn = CreateAutorecoveringConnectionWithTopologyRecoveryDisabled(); + IChannel ch = conn.CreateChannel(); + string s = "dotnet-client.test.recovery.q2"; + ch.QueueDelete(s); + ch.QueueDeclare(s, false, true, false, null); + ch.QueueDeclarePassive(s); + Assert.True(ch.IsOpen); + + try + { + CloseAndWaitForRecovery(conn); + Assert.True(ch.IsOpen); + ch.QueueDeclarePassive(s); + Assert.Fail("Expected an exception"); + } + catch (OperationInterruptedException) + { + // expected + } + finally + { + conn.Abort(); + } + } + } +} diff --git a/projects/Unit/APIApproval.Approve.verified.txt b/projects/Test/Unit/APIApproval.Approve.verified.txt similarity index 96% rename from projects/Unit/APIApproval.Approve.verified.txt rename to projects/Test/Unit/APIApproval.Approve.verified.txt index a44010c887..728a396384 100644 --- a/projects/Unit/APIApproval.Approve.verified.txt +++ b/projects/Test/Unit/APIApproval.Approve.verified.txt @@ -1,4 +1,7 @@ [assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"Benchmarks, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d20ec856aeeb8c3153a77faa2d80e6e43b5db93224a20cc7ae384f65f142e89730e2ff0fcc5d578bbe96fa98a7196c77329efdee4579b3814c0789e5a39b51df6edd75b602a33ceabdfcf19a3feb832f31d8254168cd7ba5700dfbca301fbf8db614ba41ba18474de0a5f4c2d51c995bc3636c641c8cbe76f45717bfcb943b5")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"Common, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d20ec856aeeb8c3153a77faa2d80e6e43b5db93224a20cc7ae384f65f142e89730e2ff0fcc5d578bbe96fa98a7196c77329efdee4579b3814c0789e5a39b51df6edd75b602a33ceabdfcf19a3feb832f31d8254168cd7ba5700dfbca301fbf8db614ba41ba18474de0a5f4c2d51c995bc3636c641c8cbe76f45717bfcb943b5")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"Integration, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d20ec856aeeb8c3153a77faa2d80e6e43b5db93224a20cc7ae384f65f142e89730e2ff0fcc5d578bbe96fa98a7196c77329efdee4579b3814c0789e5a39b51df6edd75b602a33ceabdfcf19a3feb832f31d8254168cd7ba5700dfbca301fbf8db614ba41ba18474de0a5f4c2d51c995bc3636c641c8cbe76f45717bfcb943b5")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"SequentialIntegration, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d20ec856aeeb8c3153a77faa2d80e6e43b5db93224a20cc7ae384f65f142e89730e2ff0fcc5d578bbe96fa98a7196c77329efdee4579b3814c0789e5a39b51df6edd75b602a33ceabdfcf19a3feb832f31d8254168cd7ba5700dfbca301fbf8db614ba41ba18474de0a5f4c2d51c995bc3636c641c8cbe76f45717bfcb943b5")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"Unit, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d20ec856aeeb8c3153a77faa2d80e6e43b5db93224a20cc7ae384f65f142e89730e2ff0fcc5d578bbe96fa98a7196c77329efdee4579b3814c0789e5a39b51df6edd75b602a33ceabdfcf19a3feb832f31d8254168cd7ba5700dfbca301fbf8db614ba41ba18474de0a5f4c2d51c995bc3636c641c8cbe76f45717bfcb943b5")] namespace RabbitMQ.Client { @@ -403,7 +406,6 @@ namespace RabbitMQ.Client ulong NextPublishSeqNo { get; } event System.EventHandler BasicAcks; event System.EventHandler BasicNacks; - event System.EventHandler BasicRecoverOk; event System.EventHandler BasicReturn; event System.EventHandler CallbackException; event System.EventHandler ChannelShutdown; @@ -416,7 +418,9 @@ namespace RabbitMQ.Client string BasicConsume(string queue, bool autoAck, string consumerTag, bool noLocal, bool exclusive, System.Collections.Generic.IDictionary arguments, RabbitMQ.Client.IBasicConsumer consumer); System.Threading.Tasks.ValueTask BasicConsumeAsync(string queue, bool autoAck, string consumerTag, bool noLocal, bool exclusive, System.Collections.Generic.IDictionary arguments, RabbitMQ.Client.IBasicConsumer consumer); RabbitMQ.Client.BasicGetResult BasicGet(string queue, bool autoAck); + System.Threading.Tasks.ValueTask BasicGetAsync(string queue, bool autoAck); void BasicNack(ulong deliveryTag, bool multiple, bool requeue); + System.Threading.Tasks.ValueTask BasicNackAsync(ulong deliveryTag, bool multiple, bool requeue); void BasicPublish(RabbitMQ.Client.CachedString exchange, RabbitMQ.Client.CachedString routingKey, in TProperties basicProperties, System.ReadOnlyMemory body = default, bool mandatory = false) where TProperties : RabbitMQ.Client.IReadOnlyBasicProperties, RabbitMQ.Client.IAmqpHeader; void BasicPublish(string exchange, string routingKey, in TProperties basicProperties, System.ReadOnlyMemory body = default, bool mandatory = false) @@ -427,14 +431,12 @@ namespace RabbitMQ.Client where TProperties : RabbitMQ.Client.IReadOnlyBasicProperties, RabbitMQ.Client.IAmqpHeader; void BasicQos(uint prefetchSize, ushort prefetchCount, bool global); System.Threading.Tasks.ValueTask BasicQosAsync(uint prefetchSize, ushort prefetchCount, bool global); - [System.Obsolete] - void BasicRecover(bool requeue); - [System.Obsolete] - void BasicRecoverAsync(bool requeue); void BasicReject(ulong deliveryTag, bool requeue); System.Threading.Tasks.ValueTask BasicRejectAsync(ulong deliveryTag, bool requeue); void Close(ushort replyCode, string replyText, bool abort); + System.Threading.Tasks.ValueTask CloseAsync(RabbitMQ.Client.ShutdownEventArgs reason, bool abort); void ConfirmSelect(); + System.Threading.Tasks.ValueTask ConfirmSelectAsync(); uint ConsumerCount(string queue); void ExchangeBind(string destination, string source, string routingKey, System.Collections.Generic.IDictionary arguments); System.Threading.Tasks.ValueTask ExchangeBindAsync(string destination, string source, string routingKey, System.Collections.Generic.IDictionary arguments); @@ -461,10 +463,15 @@ namespace RabbitMQ.Client System.Threading.Tasks.ValueTask QueueDeleteAsync(string queue, bool ifUnused, bool ifEmpty); void QueueDeleteNoWait(string queue, bool ifUnused, bool ifEmpty); uint QueuePurge(string queue); + System.Threading.Tasks.ValueTask QueuePurgeAsync(string queue); void QueueUnbind(string queue, string exchange, string routingKey, System.Collections.Generic.IDictionary arguments); + System.Threading.Tasks.ValueTask QueueUnbindAsync(string queue, string exchange, string routingKey, System.Collections.Generic.IDictionary arguments); void TxCommit(); + System.Threading.Tasks.ValueTask TxCommitAsync(); void TxRollback(); + System.Threading.Tasks.ValueTask TxRollbackAsync(); void TxSelect(); + System.Threading.Tasks.ValueTask TxSelectAsync(); System.Threading.Tasks.Task WaitForConfirmsAsync(System.Threading.CancellationToken token = default); System.Threading.Tasks.Task WaitForConfirmsOrDieAsync(System.Threading.CancellationToken token = default); } @@ -484,6 +491,7 @@ namespace RabbitMQ.Client public static System.Threading.Tasks.ValueTask BasicPublishAsync(this RabbitMQ.Client.IChannel channel, string exchange, string routingKey, System.ReadOnlyMemory body = default, bool mandatory = false) { } public static void Close(this RabbitMQ.Client.IChannel channel) { } public static void Close(this RabbitMQ.Client.IChannel channel, ushort replyCode, string replyText) { } + public static System.Threading.Tasks.ValueTask CloseAsync(this RabbitMQ.Client.IChannel channel) { } public static void ExchangeBind(this RabbitMQ.Client.IChannel channel, string destination, string source, string routingKey, System.Collections.Generic.IDictionary arguments = null) { } public static void ExchangeBindNoWait(this RabbitMQ.Client.IChannel channel, string destination, string source, string routingKey, System.Collections.Generic.IDictionary arguments = null) { } public static void ExchangeDeclare(this RabbitMQ.Client.IChannel channel, string exchange, string type, bool durable = false, bool autoDelete = false, System.Collections.Generic.IDictionary arguments = null) { } diff --git a/projects/Unit/APIApproval.cs b/projects/Test/Unit/APIApproval.cs similarity index 98% rename from projects/Unit/APIApproval.cs rename to projects/Test/Unit/APIApproval.cs index 25e35bd8d4..8ce97b5c34 100644 --- a/projects/Unit/APIApproval.cs +++ b/projects/Test/Unit/APIApproval.cs @@ -36,7 +36,7 @@ using VerifyXunit; using Xunit; -namespace RabbitMQ.Client.Unit +namespace Test.Unit { [UsesVerify] public class APIApproval diff --git a/projects/Unit/Helper/DebugUtil.cs b/projects/Test/Unit/DebugUtil.cs similarity index 98% rename from projects/Unit/Helper/DebugUtil.cs rename to projects/Test/Unit/DebugUtil.cs index 9ee8ed76bd..34d44ce90d 100644 --- a/projects/Unit/Helper/DebugUtil.cs +++ b/projects/Test/Unit/DebugUtil.cs @@ -34,7 +34,7 @@ using System.IO; using System.Reflection; -namespace RabbitMQ.Client.Unit +namespace Test.Unit { ///Miscellaneous debugging and development utilities. /// @@ -62,7 +62,7 @@ public static void Dump(byte[] bytes, TextWriter writer) { writer.Write("{0:X2}", bytes[count + i]); } - for (int i = 0; i < (rowlen - thisRow); i++) + for (int i = 0; i < rowlen - thisRow; i++) { writer.Write(" "); } diff --git a/projects/Unit/TestAmqpTcpEndpointParsing.cs b/projects/Test/Unit/TestAmqpTcpEndpointParsing.cs similarity index 99% rename from projects/Unit/TestAmqpTcpEndpointParsing.cs rename to projects/Test/Unit/TestAmqpTcpEndpointParsing.cs index 8719bb00d4..379a5b34d0 100644 --- a/projects/Unit/TestAmqpTcpEndpointParsing.cs +++ b/projects/Test/Unit/TestAmqpTcpEndpointParsing.cs @@ -30,9 +30,10 @@ //--------------------------------------------------------------------------- using System; +using RabbitMQ.Client; using Xunit; -namespace RabbitMQ.Client.Unit +namespace Test.Unit { public class TestAmqpTcpEndpointParsing { diff --git a/projects/Unit/TestAmqpUri.cs b/projects/Test/Unit/TestAmqpUri.cs similarity index 99% rename from projects/Unit/TestAmqpUri.cs rename to projects/Test/Unit/TestAmqpUri.cs index e58ae23ae1..823f41205b 100644 --- a/projects/Unit/TestAmqpUri.cs +++ b/projects/Test/Unit/TestAmqpUri.cs @@ -30,9 +30,10 @@ //--------------------------------------------------------------------------- using System; +using RabbitMQ.Client; using Xunit; -namespace RabbitMQ.Client.Unit +namespace Test.Unit { public class TestAmqpUri { diff --git a/projects/Unit/TestBasicProperties.cs b/projects/Test/Unit/TestBasicProperties.cs similarity index 97% rename from projects/Unit/TestBasicProperties.cs rename to projects/Test/Unit/TestBasicProperties.cs index 9fdbd634b3..0eb2cc04c4 100644 --- a/projects/Unit/TestBasicProperties.cs +++ b/projects/Test/Unit/TestBasicProperties.cs @@ -30,14 +30,10 @@ //--------------------------------------------------------------------------- using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using RabbitMQ.Client.Events; +using RabbitMQ.Client; using Xunit; -namespace RabbitMQ.Client.Unit +namespace Test.Unit { public class TestBasicProperties { diff --git a/projects/Unit/TestBlockingCell.cs b/projects/Test/Unit/TestBlockingCell.cs similarity index 99% rename from projects/Unit/TestBlockingCell.cs rename to projects/Test/Unit/TestBlockingCell.cs index ecbb48db5d..9439e4298a 100644 --- a/projects/Unit/TestBlockingCell.cs +++ b/projects/Test/Unit/TestBlockingCell.cs @@ -34,7 +34,7 @@ using RabbitMQ.Util; using Xunit; -namespace RabbitMQ.Client.Unit +namespace Test.Unit { public class TestBlockingCell : TimingFixture { diff --git a/projects/Unit/TestContentHeaderCodec.cs b/projects/Test/Unit/TestContentHeaderCodec.cs similarity index 99% rename from projects/Unit/TestContentHeaderCodec.cs rename to projects/Test/Unit/TestContentHeaderCodec.cs index 72050bdf38..4429b468eb 100644 --- a/projects/Unit/TestContentHeaderCodec.cs +++ b/projects/Test/Unit/TestContentHeaderCodec.cs @@ -31,9 +31,10 @@ using System; using System.Collections.Generic; +using RabbitMQ.Client; using Xunit; -namespace RabbitMQ.Client.Unit +namespace Test.Unit { public class TestContentHeaderCodec { diff --git a/projects/Unit/TestFieldTableFormatting.cs b/projects/Test/Unit/TestFieldTableFormatting.cs similarity index 99% rename from projects/Unit/TestFieldTableFormatting.cs rename to projects/Test/Unit/TestFieldTableFormatting.cs index 211a1ca161..87232c2b87 100644 --- a/projects/Unit/TestFieldTableFormatting.cs +++ b/projects/Test/Unit/TestFieldTableFormatting.cs @@ -32,10 +32,12 @@ using System; using System.Collections; using System.Text; +using RabbitMQ; +using RabbitMQ.Client; using RabbitMQ.Client.Impl; using Xunit; -namespace RabbitMQ.Client.Unit +namespace Test.Unit { public class TestFieldTableFormatting : WireFormattingFixture { diff --git a/projects/Unit/TestFieldTableFormattingGeneric.cs b/projects/Test/Unit/TestFieldTableFormattingGeneric.cs similarity index 99% rename from projects/Unit/TestFieldTableFormattingGeneric.cs rename to projects/Test/Unit/TestFieldTableFormattingGeneric.cs index 4df0022264..03ca347c89 100644 --- a/projects/Unit/TestFieldTableFormattingGeneric.cs +++ b/projects/Test/Unit/TestFieldTableFormattingGeneric.cs @@ -33,10 +33,12 @@ using System.Collections; using System.Collections.Generic; using System.Text; +using RabbitMQ; +using RabbitMQ.Client; using RabbitMQ.Client.Impl; using Xunit; -namespace RabbitMQ.Client.Unit +namespace Test.Unit { public class TestFieldTableFormattingGeneric : WireFormattingFixture { diff --git a/projects/Unit/TestFrameFormatting.cs b/projects/Test/Unit/TestFrameFormatting.cs similarity index 87% rename from projects/Unit/TestFrameFormatting.cs rename to projects/Test/Unit/TestFrameFormatting.cs index 08e1d8427f..24afa90133 100644 --- a/projects/Unit/TestFrameFormatting.cs +++ b/projects/Test/Unit/TestFrameFormatting.cs @@ -32,17 +32,18 @@ using System; using System.Buffers; using System.Runtime.InteropServices; +using RabbitMQ.Client; using RabbitMQ.Client.Framing.Impl; using Xunit; -namespace RabbitMQ.Client.Unit +namespace Test.Unit { public class TestFrameFormatting : WireFormattingFixture { [Fact] public void HeartbeatFrame() { - Memory memory = Impl.Framing.Heartbeat.GetHeartbeatFrame(); + Memory memory = RabbitMQ.Client.Impl.Framing.Heartbeat.GetHeartbeatFrame(); Span frameSpan = memory.Span; try @@ -74,10 +75,10 @@ public void HeaderFrame() var basicProperties = new BasicProperties { AppId = "A" }; int payloadSize = ((IAmqpWriteable)basicProperties).GetRequiredBufferSize(); - byte[] frameBytes = new byte[Impl.Framing.Header.FrameSize + BodyLength + payloadSize]; - Impl.Framing.Header.WriteTo(frameBytes, Channel, ref basicProperties, BodyLength); + byte[] frameBytes = new byte[RabbitMQ.Client.Impl.Framing.Header.FrameSize + BodyLength + payloadSize]; + RabbitMQ.Client.Impl.Framing.Header.WriteTo(frameBytes, Channel, ref basicProperties, BodyLength); - Assert.Equal(20, Impl.Framing.Header.FrameSize); + Assert.Equal(20, RabbitMQ.Client.Impl.Framing.Header.FrameSize); Assert.Equal(Constants.FrameHeader, frameBytes[0]); Assert.Equal(0, frameBytes[1]); // channel Assert.Equal(Channel, frameBytes[2]); // channel @@ -111,10 +112,10 @@ public void MethodFrame() var method = new BasicPublish("E", "R", true, true); int payloadSize = method.GetRequiredBufferSize(); - byte[] frameBytes = new byte[Impl.Framing.Method.FrameSize + payloadSize]; - Impl.Framing.Method.WriteTo(frameBytes, Channel, ref method); + byte[] frameBytes = new byte[RabbitMQ.Client.Impl.Framing.Method.FrameSize + payloadSize]; + RabbitMQ.Client.Impl.Framing.Method.WriteTo(frameBytes, Channel, ref method); - Assert.Equal(12, Impl.Framing.Method.FrameSize); + Assert.Equal(12, RabbitMQ.Client.Impl.Framing.Method.FrameSize); Assert.Equal(Constants.FrameMethod, frameBytes[0]); Assert.Equal(0, frameBytes[1]); // channel Assert.Equal(Channel, frameBytes[2]); // channel @@ -142,10 +143,10 @@ public void BodySegmentFrame() const int Channel = 3; byte[] payload = new byte[4]; - byte[] frameBytes = new byte[Impl.Framing.BodySegment.FrameSize + payload.Length]; - Impl.Framing.BodySegment.WriteTo(frameBytes, Channel, payload); + byte[] frameBytes = new byte[RabbitMQ.Client.Impl.Framing.BodySegment.FrameSize + payload.Length]; + RabbitMQ.Client.Impl.Framing.BodySegment.WriteTo(frameBytes, Channel, payload); - Assert.Equal(8, Impl.Framing.BodySegment.FrameSize); + Assert.Equal(8, RabbitMQ.Client.Impl.Framing.BodySegment.FrameSize); Assert.Equal(Constants.FrameBody, frameBytes[0]); Assert.Equal(0, frameBytes[1]); // channel Assert.Equal(Channel, frameBytes[2]); // channel diff --git a/projects/Unit/TestIEndpointResolverExtensions.cs b/projects/Test/Unit/TestIEndpointResolverExtensions.cs similarity index 98% rename from projects/Unit/TestIEndpointResolverExtensions.cs rename to projects/Test/Unit/TestIEndpointResolverExtensions.cs index b09a6f6612..b9fcd81750 100644 --- a/projects/Unit/TestIEndpointResolverExtensions.cs +++ b/projects/Test/Unit/TestIEndpointResolverExtensions.cs @@ -31,9 +31,10 @@ using System; using System.Collections.Generic; +using RabbitMQ.Client; using Xunit; -namespace RabbitMQ.Client.Unit +namespace Test.Unit { public class TestEndpointResolver : IEndpointResolver { diff --git a/projects/Unit/TestIntAllocator.cs b/projects/Test/Unit/TestIntAllocator.cs similarity index 98% rename from projects/Unit/TestIntAllocator.cs rename to projects/Test/Unit/TestIntAllocator.cs index cdf81c202b..18c41bcce5 100644 --- a/projects/Unit/TestIntAllocator.cs +++ b/projects/Test/Unit/TestIntAllocator.cs @@ -34,7 +34,7 @@ using RabbitMQ.Util; using Xunit; -namespace RabbitMQ.Client.Unit +namespace Test.Unit { public class TestIntAllocator { diff --git a/projects/Unit/TestMethodArgumentCodec.cs b/projects/Test/Unit/TestMethodArgumentCodec.cs similarity index 99% rename from projects/Unit/TestMethodArgumentCodec.cs rename to projects/Test/Unit/TestMethodArgumentCodec.cs index cf8b9cf572..7c8f279c9e 100644 --- a/projects/Unit/TestMethodArgumentCodec.cs +++ b/projects/Test/Unit/TestMethodArgumentCodec.cs @@ -32,10 +32,11 @@ using System; using System.Collections; using System.Text; +using RabbitMQ; using RabbitMQ.Client.Impl; using Xunit; -namespace RabbitMQ.Client.Unit +namespace Test.Unit { public class TestMethodArgumentCodec { diff --git a/projects/Unit/TestNetworkByteOrderSerialization.cs b/projects/Test/Unit/TestNetworkByteOrderSerialization.cs similarity index 99% rename from projects/Unit/TestNetworkByteOrderSerialization.cs rename to projects/Test/Unit/TestNetworkByteOrderSerialization.cs index 95368903fa..a0d0e5cf95 100644 --- a/projects/Unit/TestNetworkByteOrderSerialization.cs +++ b/projects/Test/Unit/TestNetworkByteOrderSerialization.cs @@ -30,10 +30,11 @@ //--------------------------------------------------------------------------- using System; +using RabbitMQ; using RabbitMQ.Util; using Xunit; -namespace RabbitMQ.Client.Unit +namespace Test.Unit { public class TestNetworkByteOrderSerialization { diff --git a/projects/Unit/TestPublicationAddress.cs b/projects/Test/Unit/TestPublicationAddress.cs similarity index 98% rename from projects/Unit/TestPublicationAddress.cs rename to projects/Test/Unit/TestPublicationAddress.cs index 824dcc5218..2d570bcf7f 100644 --- a/projects/Unit/TestPublicationAddress.cs +++ b/projects/Test/Unit/TestPublicationAddress.cs @@ -30,9 +30,10 @@ //--------------------------------------------------------------------------- using System; +using RabbitMQ.Client; using Xunit; -namespace RabbitMQ.Client.Unit +namespace Test.Unit { public class TestPublicationAddress { diff --git a/projects/Unit/TestRpcContinuationQueue.cs b/projects/Test/Unit/TestRpcContinuationQueue.cs similarity index 98% rename from projects/Unit/TestRpcContinuationQueue.cs rename to projects/Test/Unit/TestRpcContinuationQueue.cs index 8ec90ea162..f3fcfaea80 100644 --- a/projects/Unit/TestRpcContinuationQueue.cs +++ b/projects/Test/Unit/TestRpcContinuationQueue.cs @@ -33,7 +33,7 @@ using RabbitMQ.Client.Impl; using Xunit; -namespace RabbitMQ.Client.Unit +namespace Test.Unit { public class TestRpcContinuationQueue { diff --git a/projects/Unit/TestTcpClientAdapter.cs b/projects/Test/Unit/TestTcpClientAdapter.cs similarity index 98% rename from projects/Unit/TestTcpClientAdapter.cs rename to projects/Test/Unit/TestTcpClientAdapter.cs index f3f9009484..14ef1aa3ff 100644 --- a/projects/Unit/TestTcpClientAdapter.cs +++ b/projects/Test/Unit/TestTcpClientAdapter.cs @@ -34,7 +34,7 @@ using RabbitMQ.Client.Impl; using Xunit; -namespace RabbitMQ.Client.Unit +namespace Test.Unit { public class TestTcpClientAdapter { diff --git a/projects/Unit/TestTimerBasedCredentialRefresher.cs b/projects/Test/Unit/TestTimerBasedCredentialRefresher.cs similarity index 99% rename from projects/Unit/TestTimerBasedCredentialRefresher.cs rename to projects/Test/Unit/TestTimerBasedCredentialRefresher.cs index 4d047e9d80..367f7caa7c 100644 --- a/projects/Unit/TestTimerBasedCredentialRefresher.cs +++ b/projects/Test/Unit/TestTimerBasedCredentialRefresher.cs @@ -31,10 +31,11 @@ using System; using System.Threading; +using RabbitMQ.Client; using Xunit; using Xunit.Abstractions; -namespace RabbitMQ.Client.Unit +namespace Test.Unit { public class MockCredentialsProvider : ICredentialsProvider { diff --git a/projects/RabbitMQ.Client/client/framing/BasicRecover.cs b/projects/Test/Unit/TimingFixture.cs similarity index 67% rename from projects/RabbitMQ.Client/client/framing/BasicRecover.cs rename to projects/Test/Unit/TimingFixture.cs index 471e9b2274..fff7d4b031 100644 --- a/projects/RabbitMQ.Client/client/framing/BasicRecover.cs +++ b/projects/Test/Unit/TimingFixture.cs @@ -30,30 +30,17 @@ //--------------------------------------------------------------------------- using System; -using RabbitMQ.Client.client.framing; -using RabbitMQ.Client.Impl; -namespace RabbitMQ.Client.Framing.Impl +namespace Test.Unit { - internal readonly struct BasicRecover : IOutgoingAmqpMethod + public class TimingFixture { - public readonly bool _requeue; - - public BasicRecover(bool Requeue) - { - _requeue = Requeue; - } - - public ProtocolCommandId ProtocolCommandId => ProtocolCommandId.BasicRecover; - - public int WriteTo(Span span) - { - return WireFormatting.WriteBits(ref span.GetStart(), _requeue); - } - - public int GetRequiredBufferSize() - { - return 1; // bytes for bit fields - } + public static readonly TimeSpan TimingInterval = TimeSpan.FromMilliseconds(300); + public static readonly TimeSpan TimingInterval_2X = TimeSpan.FromMilliseconds(600); + public static readonly TimeSpan TimingInterval_4X = TimeSpan.FromMilliseconds(1200); + public static readonly TimeSpan TimingInterval_8X = TimeSpan.FromMilliseconds(2400); + public static readonly TimeSpan TimingInterval_16X = TimeSpan.FromMilliseconds(4800); + public static readonly TimeSpan SafetyMargin = TimeSpan.FromMilliseconds(150); + public static readonly TimeSpan TestTimeout = TimeSpan.FromSeconds(5); } } diff --git a/projects/Unit/Unit.csproj b/projects/Test/Unit/Unit.csproj similarity index 79% rename from projects/Unit/Unit.csproj rename to projects/Test/Unit/Unit.csproj index 77e4d3ed39..75bae9fff7 100644 --- a/projects/Unit/Unit.csproj +++ b/projects/Test/Unit/Unit.csproj @@ -9,14 +9,15 @@ - ../rabbit.snk + ../../rabbit.snk true latest 7.0 + true - + @@ -26,12 +27,12 @@ all - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + diff --git a/projects/Unit/WireFormattingFixture.cs b/projects/Test/Unit/WireFormattingFixture.cs similarity index 98% rename from projects/Unit/WireFormattingFixture.cs rename to projects/Test/Unit/WireFormattingFixture.cs index ce1443f1f0..36986070c8 100644 --- a/projects/Unit/WireFormattingFixture.cs +++ b/projects/Test/Unit/WireFormattingFixture.cs @@ -32,7 +32,7 @@ using System; using Xunit; -namespace RabbitMQ.Client.Unit +namespace Test.Unit { public class WireFormattingFixture { diff --git a/projects/Unit/Fixtures.cs b/projects/Unit/Fixtures.cs deleted file mode 100644 index 1f5869b7b2..0000000000 --- a/projects/Unit/Fixtures.cs +++ /dev/null @@ -1,514 +0,0 @@ -// This source code is dual-licensed under the Apache License, version -// 2.0, and the Mozilla Public License, version 2.0. -// -// The APL v2.0: -// -//--------------------------------------------------------------------------- -// Copyright (c) 2007-2020 VMware, Inc. -// -// 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 -// -// https://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. -//--------------------------------------------------------------------------- -// -// The MPL v2.0: -// -//--------------------------------------------------------------------------- -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. -// -// Copyright (c) 2007-2020 VMware, Inc. All rights reserved. -//--------------------------------------------------------------------------- - -#pragma warning disable 2002 - -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Text; -using System.Threading; -using RabbitMQ.Client.Framing.Impl; -using Xunit; -using Xunit.Abstractions; - -namespace RabbitMQ.Client.Unit -{ - [Collection("IntegrationFixture")] - public class IntegrationFixture : IDisposable - { - internal IConnectionFactory _connFactory; - internal IConnection _conn; - internal IChannel _channel; - internal Encoding _encoding = new UTF8Encoding(); - - protected readonly TimeSpan _waitSpan; - protected readonly ITestOutputHelper _output; - protected readonly string _testDisplayName; - - public static TimeSpan RECOVERY_INTERVAL = TimeSpan.FromSeconds(2); - - public IntegrationFixture(ITestOutputHelper output) - { - _output = output; - var type = _output.GetType(); - var testMember = type.GetField("test", BindingFlags.Instance | BindingFlags.NonPublic); - var test = (ITest)testMember.GetValue(output); - _testDisplayName = test.DisplayName; - - SetUp(); - - if (IsRunningInCI()) - { - _waitSpan = TimeSpan.FromSeconds(30); - } - else - { - _waitSpan = TimeSpan.FromSeconds(10); - } - } - - protected virtual void SetUp() - { - _connFactory = new ConnectionFactory(); - _conn = _connFactory.CreateConnection(); - _channel = _conn.CreateChannel(); - } - - public virtual void Dispose() - { - if (_channel.IsOpen) - { - _channel.Close(); - } - - if (_conn.IsOpen) - { - _conn.Close(); - } - - ReleaseResources(); - } - - protected virtual void ReleaseResources() - { - // no-op - } - - // - // Connections - // - - internal AutorecoveringConnection CreateAutorecoveringConnection() - { - return CreateAutorecoveringConnection(RECOVERY_INTERVAL); - } - - internal AutorecoveringConnection CreateAutorecoveringConnection(ICredentialsProvider credentialsProvider) - { - return CreateAutorecoveringConnection(RECOVERY_INTERVAL, credentialsProvider); - } - - internal AutorecoveringConnection CreateAutorecoveringConnection(IList hostnames) - { - return CreateAutorecoveringConnection(RECOVERY_INTERVAL, hostnames); - } - - internal AutorecoveringConnection CreateAutorecoveringConnection(TimeSpan interval, ICredentialsProvider credentialsProvider = null) - { - var cf = new ConnectionFactory - { - AutomaticRecoveryEnabled = true, - CredentialsProvider = credentialsProvider, - NetworkRecoveryInterval = interval - }; - return (AutorecoveringConnection)cf.CreateConnection($"{_testDisplayName}:{Guid.NewGuid()}"); - } - - internal AutorecoveringConnection CreateAutorecoveringConnection(TimeSpan interval, IList hostnames) - { - var cf = new ConnectionFactory - { - AutomaticRecoveryEnabled = true, - // tests that use this helper will likely list unreachable hosts, - // make sure we time out quickly on those - RequestedConnectionTimeout = TimeSpan.FromSeconds(1), - NetworkRecoveryInterval = interval - }; - return (AutorecoveringConnection)cf.CreateConnection(hostnames, $"{_testDisplayName}:{Guid.NewGuid()}"); - } - - internal AutorecoveringConnection CreateAutorecoveringConnection(IList endpoints) - { - var cf = new ConnectionFactory - { - AutomaticRecoveryEnabled = true, - // tests that use this helper will likely list unreachable hosts, - // make sure we time out quickly on those - RequestedConnectionTimeout = TimeSpan.FromSeconds(1), - NetworkRecoveryInterval = RECOVERY_INTERVAL - }; - return (AutorecoveringConnection)cf.CreateConnection(endpoints, $"{_testDisplayName}:{Guid.NewGuid()}"); - } - - internal AutorecoveringConnection CreateAutorecoveringConnectionWithTopologyRecoveryDisabled() - { - var cf = new ConnectionFactory - { - AutomaticRecoveryEnabled = true, - TopologyRecoveryEnabled = false, - NetworkRecoveryInterval = RECOVERY_INTERVAL - }; - return (AutorecoveringConnection)cf.CreateConnection($"{_testDisplayName}:{Guid.NewGuid()}"); - } - - internal AutorecoveringConnection CreateAutorecoveringConnectionWithTopologyRecoveryFilter(TopologyRecoveryFilter filter) - { - var cf = new ConnectionFactory - { - AutomaticRecoveryEnabled = true, - TopologyRecoveryEnabled = true, - TopologyRecoveryFilter = filter - }; - - return (AutorecoveringConnection)cf.CreateConnection($"{_testDisplayName}:{Guid.NewGuid()}"); - } - - internal AutorecoveringConnection CreateAutorecoveringConnectionWithTopologyRecoveryExceptionHandler(TopologyRecoveryExceptionHandler handler) - { - var cf = new ConnectionFactory - { - AutomaticRecoveryEnabled = true, - TopologyRecoveryEnabled = true, - TopologyRecoveryExceptionHandler = handler - }; - - return (AutorecoveringConnection)cf.CreateConnection($"{_testDisplayName}:{Guid.NewGuid()}"); - } - - internal IConnection CreateConnectionWithContinuationTimeout(bool automaticRecoveryEnabled, TimeSpan continuationTimeout) - { - var cf = new ConnectionFactory - { - AutomaticRecoveryEnabled = automaticRecoveryEnabled, - ContinuationTimeout = continuationTimeout - }; - return cf.CreateConnection($"{_testDisplayName}:{Guid.NewGuid()}"); - } - - // - // Channels - // - - internal void WithTemporaryChannel(Action action) - { - IChannel channel = _conn.CreateChannel(); - - try - { - action(channel); - } - finally - { - channel.Abort(); - } - } - - internal void WithClosedChannel(Action action) - { - IChannel channel = _conn.CreateChannel(); - channel.Close(); - - action(channel); - } - - internal bool WaitForConfirms(IChannel m) - { - using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(4)); - return m.WaitForConfirmsAsync(cts.Token).GetAwaiter().GetResult(); - } - - // - // Exchanges - // - - internal string GenerateExchangeName() - { - return $"exchange{Guid.NewGuid()}"; - } - - internal byte[] RandomMessageBody() - { - return _encoding.GetBytes(Guid.NewGuid().ToString()); - } - - internal string DeclareNonDurableExchange(IChannel m, string x) - { - m.ExchangeDeclare(x, "fanout", false); - return x; - } - - internal string DeclareNonDurableExchangeNoWait(IChannel m, string x) - { - m.ExchangeDeclareNoWait(x, "fanout", false, false, null); - return x; - } - - // - // Queues - // - - internal string GenerateQueueName() - { - return $"queue{Guid.NewGuid()}"; - } - - internal void WithTemporaryNonExclusiveQueue(Action action) - { - WithTemporaryNonExclusiveQueue(_channel, action); - } - - internal void WithTemporaryNonExclusiveQueue(IChannel channel, Action action) - { - WithTemporaryNonExclusiveQueue(channel, action, GenerateQueueName()); - } - - internal void WithTemporaryNonExclusiveQueue(IChannel channel, Action action, string queue) - { - try - { - channel.QueueDeclare(queue, false, false, false, null); - action(channel, queue); - } - finally - { - WithTemporaryChannel(tm => tm.QueueDelete(queue)); - } - } - - internal void WithTemporaryQueueNoWait(IChannel channel, Action action, string queue) - { - try - { - channel.QueueDeclareNoWait(queue, false, true, false, null); - action(channel, queue); - } - finally - { - WithTemporaryChannel(x => x.QueueDelete(queue)); - } - } - - internal void EnsureNotEmpty(string q, string body) - { - WithTemporaryChannel(x => x.BasicPublish("", q, _encoding.GetBytes(body))); - } - - internal void WithNonEmptyQueue(Action action) - { - WithNonEmptyQueue(action, "msg"); - } - - internal void WithNonEmptyQueue(Action action, string msg) - { - WithTemporaryNonExclusiveQueue((m, q) => - { - EnsureNotEmpty(q, msg); - action(m, q); - }); - } - - internal void WithEmptyQueue(Action action) - { - WithTemporaryNonExclusiveQueue((channel, queue) => - { - channel.QueuePurge(queue); - action(channel, queue); - }); - } - - internal void AssertMessageCount(string q, uint count) - { - WithTemporaryChannel((m) => - { - QueueDeclareOk ok = m.QueueDeclarePassive(q); - Assert.Equal(count, ok.MessageCount); - }); - } - - internal void AssertConsumerCount(string q, int count) - { - WithTemporaryChannel((m) => - { - QueueDeclareOk ok = m.QueueDeclarePassive(q); - Assert.Equal((uint)count, ok.ConsumerCount); - }); - } - - internal void AssertConsumerCount(IChannel m, string q, uint count) - { - QueueDeclareOk ok = m.QueueDeclarePassive(q); - Assert.Equal(count, ok.ConsumerCount); - } - - // - // Shutdown - // - - internal void AssertShutdownError(ShutdownEventArgs args, int code) - { - Assert.Equal(args.ReplyCode, code); - } - - internal void AssertPreconditionFailed(ShutdownEventArgs args) - { - AssertShutdownError(args, Constants.PreconditionFailed); - } - - // - // Concurrency - // - - internal void WaitOn(object o) - { - lock (o) - { - Monitor.Wait(o, TimingFixture.TestTimeout); - } - } - - // - // Flow Control - // - - internal void Block() - { - RabbitMQCtl.Block(_conn, _encoding); - } - - internal void Unblock() - { - RabbitMQCtl.Unblock(); - } - - // - // Connection Closure - // - - internal void CloseConnection(IConnection conn) - { - RabbitMQCtl.CloseConnection(conn); - } - - internal void CloseAllConnections() - { - RabbitMQCtl.CloseAllConnections(); - } - - internal void RestartRabbitMQ() - { - RabbitMQCtl.RestartRabbitMQ(); - } - - internal void StopRabbitMQ() - { - RabbitMQCtl.StopRabbitMQ(); - } - - internal void StartRabbitMQ() - { - RabbitMQCtl.StartRabbitMQ(); - RabbitMQCtl.AwaitRabbitMQ(); - } - - // - // Concurrency and Coordination - // - - internal void Wait(ManualResetEventSlim latch) - { - Assert.True(latch.Wait(_waitSpan), "waiting on a latch timed out"); - } - - internal void Wait(ManualResetEventSlim latch, TimeSpan timeSpan) - { - Assert.True(latch.Wait(timeSpan), "waiting on a latch timed out"); - } - - private static bool IsRunningInCI() - { - if (bool.TryParse(Environment.GetEnvironmentVariable("CI"), out bool ci)) - { - if (ci == true) - { - return true; - } - } - - string concourse = Environment.GetEnvironmentVariable("CONCOURSE_CI_BUILD"); - string gha = Environment.GetEnvironmentVariable("GITHUB_ACTIONS"); - if (String.IsNullOrWhiteSpace(concourse) && String.IsNullOrWhiteSpace(gha)) - { - return false; - } - - return true; - } - } - - public sealed class IgnoreOnVersionsEarlierThan : FactAttribute - { - public IgnoreOnVersionsEarlierThan(int major, int minor) - { - if (!CheckMiniumVersion(new Version(major, minor))) - { - Skip = $"Skipped test. It requires RabbitMQ +{major}.{minor}"; - } - } - - private bool CheckMiniumVersion(Version miniumVersion) - { - using (var _conn = new ConnectionFactory().CreateConnection()) - { - System.Collections.Generic.IDictionary properties = _conn.ServerProperties; - - if (properties.TryGetValue("version", out object versionVal)) - { - string versionStr = Encoding.UTF8.GetString((byte[])versionVal); - - int dashIdx = Math.Max(versionStr.IndexOf('-'), versionStr.IndexOf('+')); - if (dashIdx > 0) - { - versionStr = versionStr.Remove(dashIdx); - } - - if (Version.TryParse(versionStr, out Version version)) - { - return version >= miniumVersion; - } - } - - return false; - } - } - } - - public class TimingFixture - { - public static readonly TimeSpan TimingInterval = TimeSpan.FromMilliseconds(300); - public static readonly TimeSpan TimingInterval_2X = TimeSpan.FromMilliseconds(600); - public static readonly TimeSpan TimingInterval_4X = TimeSpan.FromMilliseconds(1200); - public static readonly TimeSpan TimingInterval_8X = TimeSpan.FromMilliseconds(2400); - public static readonly TimeSpan TimingInterval_16X = TimeSpan.FromMilliseconds(4800); - public static readonly TimeSpan SafetyMargin = TimeSpan.FromMilliseconds(150); - public static readonly TimeSpan TestTimeout = TimeSpan.FromSeconds(5); - } -} diff --git a/projects/Unit/TestAsyncConsumer.cs b/projects/Unit/TestAsyncConsumer.cs deleted file mode 100644 index 5ab094a360..0000000000 --- a/projects/Unit/TestAsyncConsumer.cs +++ /dev/null @@ -1,358 +0,0 @@ -// This source code is dual-licensed under the Apache License, version -// 2.0, and the Mozilla Public License, version 2.0. -// -// The APL v2.0: -// -//--------------------------------------------------------------------------- -// Copyright (c) 2007-2020 VMware, Inc. -// -// 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 -// -// https://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. -//--------------------------------------------------------------------------- -// -// The MPL v2.0: -// -//--------------------------------------------------------------------------- -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. -// -// Copyright (c) 2007-2020 VMware, Inc. All rights reserved. -//--------------------------------------------------------------------------- - -using System; -using System.Security.Cryptography; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using RabbitMQ.Client.Events; -using Xunit; -using Xunit.Abstractions; - -namespace RabbitMQ.Client.Unit -{ - [Collection("IntegrationFixture")] - public class TestAsyncConsumer - { - private readonly ITestOutputHelper _output; - - public TestAsyncConsumer(ITestOutputHelper output) - { - _output = output; - } - - [Fact] - public void TestBasicRoundtrip() - { - var cf = new ConnectionFactory { DispatchConsumersAsync = true }; - using (IConnection c = cf.CreateConnection()) - { - using (IChannel m = c.CreateChannel()) - { - QueueDeclareOk q = m.QueueDeclare(); - byte[] body = System.Text.Encoding.UTF8.GetBytes("async-hi"); - m.BasicPublish("", q.QueueName, body); - var consumer = new AsyncEventingBasicConsumer(m); - var are = new AutoResetEvent(false); - consumer.Received += async (o, a) => - { - are.Set(); - await Task.Yield(); - }; - string tag = m.BasicConsume(q.QueueName, true, consumer); - // ensure we get a delivery - bool waitRes = are.WaitOne(2000); - Assert.True(waitRes); - // unsubscribe and ensure no further deliveries - m.BasicCancel(tag); - m.BasicPublish("", q.QueueName, body); - bool waitResFalse = are.WaitOne(2000); - Assert.False(waitResFalse); - } - } - } - - [Fact] - public async Task TestBasicRoundtripConcurrent() - { - var cf = new ConnectionFactory { DispatchConsumersAsync = true, ConsumerDispatchConcurrency = 2 }; - using (IConnection c = cf.CreateConnection()) - { - using (IChannel m = c.CreateChannel()) - { - QueueDeclareOk q = m.QueueDeclare(); - string publish1 = get_unique_string(1024); - byte[] body = Encoding.UTF8.GetBytes(publish1); - m.BasicPublish("", q.QueueName, body); - - string publish2 = get_unique_string(1024); - body = Encoding.UTF8.GetBytes(publish2); - m.BasicPublish("", q.QueueName, body); - - var consumer = new AsyncEventingBasicConsumer(m); - - var publish1SyncSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var publish2SyncSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var maximumWaitTime = TimeSpan.FromSeconds(10); - var tokenSource = new CancellationTokenSource(maximumWaitTime); - tokenSource.Token.Register(() => - { - publish1SyncSource.TrySetResult(false); - publish2SyncSource.TrySetResult(false); - }); - - consumer.Received += async (o, a) => - { - string decoded = Encoding.ASCII.GetString(a.Body.ToArray()); - if (decoded == publish1) - { - publish1SyncSource.TrySetResult(true); - await publish2SyncSource.Task; - } - else if (decoded == publish2) - { - publish2SyncSource.TrySetResult(true); - await publish1SyncSource.Task; - } - }; - - m.BasicConsume(q.QueueName, true, consumer); - - // ensure we get a delivery - await Task.WhenAll(publish1SyncSource.Task, publish2SyncSource.Task); - - bool result1 = await publish1SyncSource.Task; - Assert.True(result1, $"1 - Non concurrent dispatch lead to deadlock after {maximumWaitTime}"); - - bool result2 = await publish2SyncSource.Task; - Assert.True(result2, $"2 - Non concurrent dispatch lead to deadlock after {maximumWaitTime}"); - } - } - } - - [Fact] - public async Task TestBasicRoundtripConcurrentManyMessages() - { - const int publish_total = 4096; - string queueName = $"{nameof(TestBasicRoundtripConcurrentManyMessages)}-{Guid.NewGuid()}"; - - string publish1 = get_unique_string(32768); - byte[] body1 = Encoding.ASCII.GetBytes(publish1); - string publish2 = get_unique_string(32768); - byte[] body2 = Encoding.ASCII.GetBytes(publish2); - - var cf = new ConnectionFactory { DispatchConsumersAsync = true, ConsumerDispatchConcurrency = 2 }; - - using (IConnection c = cf.CreateConnection()) - { - using (IChannel m = c.CreateChannel()) - { - QueueDeclareOk q = m.QueueDeclare(queue: queueName, exclusive: false, durable: true); - Assert.Equal(q.QueueName, queueName); - } - } - - Task publishTask = Task.Run(async () => - { - using (IConnection c = cf.CreateConnection()) - { - using (IChannel m = c.CreateChannel()) - { - QueueDeclareOk q = m.QueueDeclare(queue: queueName, exclusive: false, durable: true); - for (int i = 0; i < publish_total; i++) - { - await m.BasicPublishAsync(string.Empty, queueName, body1); - await m.BasicPublishAsync(string.Empty, queueName, body2); - } - } - } - }); - - Task consumeTask = Task.Run(async () => - { - var publish1SyncSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var publish2SyncSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var maximumWaitTime = TimeSpan.FromSeconds(30); - var tokenSource = new CancellationTokenSource(maximumWaitTime); - tokenSource.Token.Register(() => - { - publish1SyncSource.TrySetResult(false); - publish2SyncSource.TrySetResult(false); - }); - - using (IConnection c = cf.CreateConnection()) - { - using (IChannel m = c.CreateChannel()) - { - var consumer = new AsyncEventingBasicConsumer(m); - - int publish1_count = 0; - int publish2_count = 0; - - consumer.Received += async (o, a) => - { - string decoded = Encoding.ASCII.GetString(a.Body.ToArray()); - if (decoded == publish1) - { - if (Interlocked.Increment(ref publish1_count) >= publish_total) - { - publish1SyncSource.TrySetResult(true); - await publish2SyncSource.Task; - } - } - else if (decoded == publish2) - { - if (Interlocked.Increment(ref publish2_count) >= publish_total) - { - publish2SyncSource.TrySetResult(true); - await publish1SyncSource.Task; - } - } - }; - - m.BasicConsume(queueName, true, consumer); - - // ensure we get a delivery - await Task.WhenAll(publish1SyncSource.Task, publish2SyncSource.Task); - - bool result1 = await publish1SyncSource.Task; - Assert.True(result1, $"Non concurrent dispatch lead to deadlock after {maximumWaitTime}"); - - bool result2 = await publish2SyncSource.Task; - Assert.True(result2, $"Non concurrent dispatch lead to deadlock after {maximumWaitTime}"); - } - } - }); - - await Task.WhenAll(publishTask, consumeTask); - } - - [Fact] - public void TestBasicRoundtripNoWait() - { - var cf = new ConnectionFactory { DispatchConsumersAsync = true }; - using (IConnection c = cf.CreateConnection()) - { - using (IChannel m = c.CreateChannel()) - { - QueueDeclareOk q = m.QueueDeclare(); - byte[] body = System.Text.Encoding.UTF8.GetBytes("async-hi"); - m.BasicPublish("", q.QueueName, body); - var consumer = new AsyncEventingBasicConsumer(m); - var are = new AutoResetEvent(false); - consumer.Received += async (o, a) => - { - are.Set(); - await Task.Yield(); - }; - string tag = m.BasicConsume(q.QueueName, true, consumer); - // ensure we get a delivery - bool waitRes = are.WaitOne(2000); - Assert.True(waitRes); - // unsubscribe and ensure no further deliveries - m.BasicCancelNoWait(tag); - m.BasicPublish("", q.QueueName, body); - bool waitResFalse = are.WaitOne(2000); - Assert.False(waitResFalse); - } - } - } - - [Fact] - public async void ConcurrentEventingTestForReceived() - { - const int NumberOfThreads = 4; - const int NumberOfRegistrations = 5000; - - var called = new byte[NumberOfThreads * NumberOfRegistrations]; - - var cf = new ConnectionFactory { DispatchConsumersAsync = true }; - using (IConnection c = cf.CreateConnection()) - { - using (IChannel m = c.CreateChannel()) - { - QueueDeclareOk q = m.QueueDeclare(); - var consumer = new AsyncEventingBasicConsumer(m); - m.BasicConsume(q.QueueName, true, consumer); - var countdownEvent = new CountdownEvent(NumberOfThreads); - var tasks = new Task[NumberOfThreads]; - for (int i = 0; i < NumberOfThreads; i++) - { - int threadIndex = i; - tasks[i] = Task.Run(() => - { - countdownEvent.Signal(); - countdownEvent.Wait(); - int start = threadIndex * NumberOfRegistrations; - for (int j = start; j < start + NumberOfRegistrations; j++) - { - int receivedIndex = j; - consumer.Received += (sender, eventArgs) => - { - called[receivedIndex] = 1; - return Task.CompletedTask; - }; - } - }); - } - - countdownEvent.Wait(); - await Task.WhenAll(tasks); - - // Add last receiver - var are = new AutoResetEvent(false); - consumer.Received += (o, a) => - { - are.Set(); - return Task.CompletedTask; - }; - - // Send message - m.BasicPublish("", q.QueueName, ReadOnlyMemory.Empty); - are.WaitOne(TimingFixture.TestTimeout); - } - } - - // Check received messages - Assert.Equal(-1, called.AsSpan().IndexOf((byte)0)); - } - - [Fact] - public void NonAsyncConsumerShouldThrowInvalidOperationException() - { - var cf = new ConnectionFactory { DispatchConsumersAsync = true }; - using (IConnection c = cf.CreateConnection()) - { - using (IChannel m = c.CreateChannel()) - { - QueueDeclareOk q = m.QueueDeclare(); - byte[] body = System.Text.Encoding.UTF8.GetBytes("async-hi"); - m.BasicPublish("", q.QueueName, body); - var consumer = new EventingBasicConsumer(m); - Assert.Throws(() => m.BasicConsume(q.QueueName, false, consumer)); - } - } - } - - private string get_unique_string(int string_length) - { - using (var rng = RandomNumberGenerator.Create()) - { - var bit_count = (string_length * 6); - var byte_count = ((bit_count + 7) / 8); // rounded up - var bytes = new byte[byte_count]; - rng.GetBytes(bytes); - return Convert.ToBase64String(bytes); - } - } - } -} diff --git a/projects/Unit/TestBasicPublish.cs b/projects/Unit/TestBasicPublish.cs deleted file mode 100644 index 518a6e413a..0000000000 --- a/projects/Unit/TestBasicPublish.cs +++ /dev/null @@ -1,296 +0,0 @@ -// This source code is dual-licensed under the Apache License, version -// 2.0, and the Mozilla Public License, version 2.0. -// -// The APL v2.0: -// -//--------------------------------------------------------------------------- -// Copyright (c) 2007-2020 VMware, Inc. -// -// 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 -// -// https://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. -//--------------------------------------------------------------------------- -// -// The MPL v2.0: -// -//--------------------------------------------------------------------------- -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. -// -// Copyright (c) 2011-2020 VMware, Inc. or its affiliates. All rights reserved. -//--------------------------------------------------------------------------- - -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using RabbitMQ.Client.Events; -using Xunit; -using Xunit.Sdk; - -namespace RabbitMQ.Client.Unit -{ - [Collection("IntegrationFixture")] - public class TestBasicPublish - { - [Fact] - public void TestBasicRoundtripArray() - { - var cf = new ConnectionFactory(); - using (IConnection c = cf.CreateConnection()) - using (IChannel m = c.CreateChannel()) - { - QueueDeclareOk q = m.QueueDeclare(); - var bp = new BasicProperties(); - byte[] sendBody = System.Text.Encoding.UTF8.GetBytes("hi"); - byte[] consumeBody = null; - var consumer = new EventingBasicConsumer(m); - var are = new AutoResetEvent(false); - consumer.Received += async (o, a) => - { - consumeBody = a.Body.ToArray(); - are.Set(); - await Task.Yield(); - }; - string tag = m.BasicConsume(q.QueueName, true, consumer); - - m.BasicPublish("", q.QueueName, bp, sendBody); - bool waitResFalse = are.WaitOne(5000); - m.BasicCancel(tag); - - Assert.True(waitResFalse); - Assert.Equal(sendBody, consumeBody); - } - } - - [Fact] - public void TestBasicRoundtripCachedString() - { - var cf = new ConnectionFactory(); - using (IConnection c = cf.CreateConnection()) - using (IChannel m = c.CreateChannel()) - { - CachedString exchangeName = new CachedString(string.Empty); - CachedString queueName = new CachedString(m.QueueDeclare().QueueName); - byte[] sendBody = System.Text.Encoding.UTF8.GetBytes("hi"); - byte[] consumeBody = null; - var consumer = new EventingBasicConsumer(m); - var are = new AutoResetEvent(false); - consumer.Received += async (o, a) => - { - consumeBody = a.Body.ToArray(); - are.Set(); - await Task.Yield(); - }; - string tag = m.BasicConsume(queueName.Value, true, consumer); - - m.BasicPublish(exchangeName, queueName, sendBody); - bool waitResFalse = are.WaitOne(2000); - m.BasicCancel(tag); - - Assert.True(waitResFalse); - Assert.Equal(sendBody, consumeBody); - } - } - - [Fact] - public void TestBasicRoundtripReadOnlyMemory() - { - var cf = new ConnectionFactory(); - using (IConnection c = cf.CreateConnection()) - using (IChannel m = c.CreateChannel()) - { - QueueDeclareOk q = m.QueueDeclare(); - byte[] sendBody = System.Text.Encoding.UTF8.GetBytes("hi"); - byte[] consumeBody = null; - var consumer = new EventingBasicConsumer(m); - var are = new AutoResetEvent(false); - consumer.Received += async (o, a) => - { - consumeBody = a.Body.ToArray(); - are.Set(); - await Task.Yield(); - }; - string tag = m.BasicConsume(q.QueueName, true, consumer); - - m.BasicPublish("", q.QueueName, new ReadOnlyMemory(sendBody)); - bool waitResFalse = are.WaitOne(2000); - m.BasicCancel(tag); - - Assert.True(waitResFalse); - Assert.Equal(sendBody, consumeBody); - } - } - - [Fact] - public void CanNotModifyPayloadAfterPublish() - { - var cf = new ConnectionFactory(); - using (IConnection c = cf.CreateConnection()) - using (IChannel m = c.CreateChannel()) - { - QueueDeclareOk q = m.QueueDeclare(); - byte[] sendBody = new byte[1000]; - var consumer = new EventingBasicConsumer(m); - var receivedMessage = new AutoResetEvent(false); - bool modified = true; - consumer.Received += (o, a) => - { - if (a.Body.Span.IndexOf((byte)1) < 0) - { - modified = false; - } - receivedMessage.Set(); - }; - string tag = m.BasicConsume(q.QueueName, true, consumer); - - m.BasicPublish("", q.QueueName, sendBody); - sendBody.AsSpan().Fill(1); - - Assert.True(receivedMessage.WaitOne(5000)); - Assert.False(modified, "Payload was modified after the return of BasicPublish"); - - m.BasicCancel(tag); - } - } - - [Fact] - public void TestMaxMessageSize() - { - var re = new ManualResetEventSlim(); - const ushort maxMsgSize = 1024; - - int count = 0; - byte[] msg0 = Encoding.UTF8.GetBytes("hi"); - - var r = new System.Random(); - byte[] msg1 = new byte[maxMsgSize * 2]; - r.NextBytes(msg1); - - var cf = new ConnectionFactory(); - cf.AutomaticRecoveryEnabled = false; - cf.TopologyRecoveryEnabled = false; - cf.MaxMessageSize = maxMsgSize; - - bool sawConnectionShutdown = false; - bool sawChannelShutdown = false; - bool sawConsumerRegistered = false; - bool sawConsumerCancelled = false; - - using (IConnection c = cf.CreateConnection()) - { - c.ConnectionShutdown += (o, a) => - { - sawConnectionShutdown = true; - }; - - Assert.Equal(maxMsgSize, cf.MaxMessageSize); - Assert.Equal(maxMsgSize, cf.Endpoint.MaxMessageSize); - Assert.Equal(maxMsgSize, c.Endpoint.MaxMessageSize); - - using (IChannel m = c.CreateChannel()) - { - m.ChannelShutdown += (o, a) => - { - sawChannelShutdown = true; - }; - - m.CallbackException += (o, a) => - { - throw new XunitException("Unexpected m.CallbackException"); - }; - - QueueDeclareOk q = m.QueueDeclare(); - - var consumer = new EventingBasicConsumer(m); - - consumer.Shutdown += (o, a) => - { - re.Set(); - }; - - consumer.Registered += (o, a) => - { - sawConsumerRegistered = true; - }; - - consumer.Unregistered += (o, a) => - { - throw new XunitException("Unexpected consumer.Unregistered"); - }; - - consumer.ConsumerCancelled += (o, a) => - { - sawConsumerCancelled = true; - }; - - consumer.Received += (o, a) => - { - Interlocked.Increment(ref count); - }; - - string tag = m.BasicConsume(q.QueueName, true, consumer); - - m.BasicPublish("", q.QueueName, msg0); - m.BasicPublish("", q.QueueName, msg1); - Assert.True(re.Wait(TimeSpan.FromSeconds(5))); - - Assert.Equal(1, count); - Assert.True(sawConnectionShutdown); - Assert.True(sawChannelShutdown); - Assert.True(sawConsumerRegistered); - Assert.True(sawConsumerCancelled); - } - } - } - - [Fact] - public void TestPropertiesRountrip_Headers() - { - // Arrange - var subject = new BasicProperties - { - Headers = new Dictionary() - }; - - var cf = new ConnectionFactory(); - using (IConnection c = cf.CreateConnection()) - using (IChannel m = c.CreateChannel()) - { - QueueDeclareOk q = m.QueueDeclare(); - var bp = new BasicProperties() { Headers = new Dictionary() }; - bp.Headers["Hello"] = "World"; - byte[] sendBody = Encoding.UTF8.GetBytes("hi"); - byte[] consumeBody = null; - var consumer = new EventingBasicConsumer(m); - var are = new AutoResetEvent(false); - string response = null; - consumer.Received += async (o, a) => - { - response = Encoding.UTF8.GetString(a.BasicProperties.Headers["Hello"] as byte[]); - consumeBody = a.Body.ToArray(); - are.Set(); - await Task.Yield(); - }; - - string tag = m.BasicConsume(q.QueueName, true, consumer); - m.BasicPublish("", q.QueueName, bp, sendBody); - bool waitResFalse = are.WaitOne(5000); - m.BasicCancel(tag); - Assert.True(waitResFalse); - Assert.Equal(sendBody, consumeBody); - Assert.Equal("World", response); - } - } - } -} diff --git a/projects/Unit/TestConcurrentAccessWithSharedConnection.cs b/projects/Unit/TestConcurrentAccessWithSharedConnection.cs deleted file mode 100644 index c6750fd7f3..0000000000 --- a/projects/Unit/TestConcurrentAccessWithSharedConnection.cs +++ /dev/null @@ -1,223 +0,0 @@ -// This source code is dual-licensed under the Apache License, version -// 2.0, and the Mozilla Public License, version 2.0. -// -// The APL v2.0: -// -//--------------------------------------------------------------------------- -// Copyright (c) 2007-2020 VMware, Inc. -// -// 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 -// -// https://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. -//--------------------------------------------------------------------------- -// -// The MPL v2.0: -// -//--------------------------------------------------------------------------- -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. -// -// Copyright (c) 2007-2020 VMware, Inc. All rights reserved. -//--------------------------------------------------------------------------- - -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Xunit; -using Xunit.Abstractions; - -namespace RabbitMQ.Client.Unit -{ - public class TestConcurrentAccessWithSharedConnection : IntegrationFixture - { - internal const int Threads = 32; - internal CountdownEvent _latch; - internal TimeSpan _completionTimeout = TimeSpan.FromSeconds(90); - - public TestConcurrentAccessWithSharedConnection(ITestOutputHelper output) : base(output) - { - } - - protected override void SetUp() - { - base.SetUp(); - ThreadPool.SetMinThreads(Threads, Threads); - _latch = new CountdownEvent(Threads); - } - - public override void Dispose() - { - base.Dispose(); - _latch.Dispose(); - } - - [Fact] - public void TestConcurrentChannelOpenAndPublishingWithBlankMessages() - { - TestConcurrentChannelOpenAndPublishingWithBody(Array.Empty(), 30); - } - - [Fact] - public void TestConcurrentChannelOpenAndPublishingSize64() - { - TestConcurrentChannelOpenAndPublishingWithBodyOfSize(64); - } - - [Fact] - public void TestConcurrentChannelOpenAndPublishingSize256() - { - TestConcurrentChannelOpenAndPublishingWithBodyOfSize(256); - } - - [Fact] - public void TestConcurrentChannelOpenAndPublishingSize1024() - { - TestConcurrentChannelOpenAndPublishingWithBodyOfSize(1024); - } - - [Fact] - public Task TestConcurrentChannelOpenAndPublishingWithBlankMessagesAsync() - { - return TestConcurrentChannelOpenAndPublishingWithBodyAsync(Array.Empty(), 30); - } - - [Fact] - public Task TestConcurrentChannelOpenAndPublishingSize64Async() - { - return TestConcurrentChannelOpenAndPublishingWithBodyOfSizeAsync(64); - } - - [Fact] - public Task TestConcurrentChannelOpenAndPublishingSize256Async() - { - return TestConcurrentChannelOpenAndPublishingWithBodyOfSizeAsync(256); - } - - [Fact] - public Task TestConcurrentChannelOpenAndPublishingSize1024Async() - { - return TestConcurrentChannelOpenAndPublishingWithBodyOfSizeAsync(1024); - } - - [Fact] - public void TestConcurrentChannelOpenCloseLoop() - { - TestConcurrentChannelOperations((conn) => - { - IChannel ch = conn.CreateChannel(); - ch.Close(); - }, 50); - } - - internal void TestConcurrentChannelOpenAndPublishingWithBodyOfSize(int length, int iterations = 30) - { - TestConcurrentChannelOpenAndPublishingWithBody(new byte[length], iterations); - } - - internal Task TestConcurrentChannelOpenAndPublishingWithBodyOfSizeAsync(int length, int iterations = 30) - { - return TestConcurrentChannelOpenAndPublishingWithBodyAsync(new byte[length], iterations); - } - - internal void TestConcurrentChannelOpenAndPublishingWithBody(byte[] body, int iterations) - { - TestConcurrentChannelOperations((conn) => - { - // publishing on a shared channel is not supported - // and would missing the point of this test anyway - IChannel ch = _conn.CreateChannel(); - ch.ConfirmSelect(); - for (int j = 0; j < 200; j++) - { - ch.BasicPublish("", "_______", body); - } - using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(40)); - ch.WaitForConfirmsAsync(cts.Token).GetAwaiter().GetResult(); - }, iterations); - } - - internal Task TestConcurrentChannelOpenAndPublishingWithBodyAsync(byte[] body, int iterations) - { - return TestConcurrentChannelOperationsAsync(async (conn) => - { - // publishing on a shared channel is not supported - // and would missing the point of this test anyway - IChannel ch = _conn.CreateChannel(); - ch.ConfirmSelect(); - for (int j = 0; j < 200; j++) - { - await ch.BasicPublishAsync("", "_______", body).ConfigureAwait(false); - } - using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(40)); - await ch.WaitForConfirmsAsync(cts.Token).ConfigureAwait(false); - }, iterations); - } - - internal void TestConcurrentChannelOperations(Action actions, - int iterations) - { - TestConcurrentChannelOperations(actions, iterations, _completionTimeout); - } - - internal Task TestConcurrentChannelOperationsAsync(Func actions, int iterations) - { - return TestConcurrentChannelOperationsAsync(actions, iterations, _completionTimeout); - } - - internal void TestConcurrentChannelOperations(Action actions, - int iterations, TimeSpan timeout) - { - _ = Enumerable.Range(0, Threads).Select(x => - { - return Task.Run(() => - { - for (int j = 0; j < iterations; j++) - { - actions(_conn); - } - - _latch.Signal(); - }); - }).ToArray(); - - Assert.True(_latch.Wait(timeout)); - // incorrect frame interleaving in these tests will result - // in an unrecoverable connection-level exception, thus - // closing the connection - Assert.True(_conn.IsOpen); - } - - internal async Task TestConcurrentChannelOperationsAsync(Func actions, - int iterations, TimeSpan timeout) - { - await Task.WhenAll(Enumerable.Range(0, Threads).Select(x => - { - return Task.Run(() => - { - for (int j = 0; j < iterations; j++) - { - actions(_conn); - } - - _latch.Signal(); - }); - }).ToArray()); - - Assert.True(_latch.Wait(timeout)); - // incorrect frame interleaving in these tests will result - // in an unrecoverable connection-level exception, thus - // closing the connection - Assert.True(_conn.IsOpen); - } - } -} diff --git a/projects/Unit/TestConsumer.cs b/projects/Unit/TestConsumer.cs deleted file mode 100644 index 2d647570a9..0000000000 --- a/projects/Unit/TestConsumer.cs +++ /dev/null @@ -1,204 +0,0 @@ -// This source code is dual-licensed under the Apache License, version -// 2.0, and the Mozilla Public License, version 2.0. -// -// The APL v2.0: -// -//--------------------------------------------------------------------------- -// Copyright (c) 2007-2020 VMware, Inc. -// -// 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 -// -// https://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. -//--------------------------------------------------------------------------- -// -// The MPL v2.0: -// -//--------------------------------------------------------------------------- -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. -// -// Copyright (c) 2007-2020 VMware, Inc. All rights reserved. -//--------------------------------------------------------------------------- - -using System; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using RabbitMQ.Client.Events; -using Xunit; -using Xunit.Abstractions; - -namespace RabbitMQ.Client.Unit -{ - [Collection("IntegrationFixture")] - public class TestConsumer - { - private readonly ITestOutputHelper _output; - - public TestConsumer(ITestOutputHelper output) => _output = output; - - [Fact] - public async Task TestBasicRoundtripConcurrent() - { - var cf = new ConnectionFactory { ConsumerDispatchConcurrency = 2 }; - using (IConnection c = cf.CreateConnection()) - using (IChannel m = c.CreateChannel()) - { - QueueDeclareOk q = m.QueueDeclare(); - const string publish1 = "sync-hi-1"; - byte[] body = Encoding.UTF8.GetBytes(publish1); - m.BasicPublish("", q.QueueName, body); - const string publish2 = "sync-hi-2"; - body = Encoding.UTF8.GetBytes(publish2); - m.BasicPublish("", q.QueueName, body); - - var consumer = new EventingBasicConsumer(m); - - var publish1SyncSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var publish2SyncSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var maximumWaitTime = TimeSpan.FromSeconds(10); - var tokenSource = new CancellationTokenSource(maximumWaitTime); - tokenSource.Token.Register(() => - { - publish1SyncSource.TrySetResult(false); - publish2SyncSource.TrySetResult(false); - }); - - consumer.Received += (o, a) => - { - switch (Encoding.UTF8.GetString(a.Body.ToArray())) - { - case publish1: - publish1SyncSource.TrySetResult(true); - break; - case publish2: - publish2SyncSource.TrySetResult(true); - break; - } - }; - - m.BasicConsume(q.QueueName, true, consumer); - - await Task.WhenAll(publish1SyncSource.Task, publish2SyncSource.Task); - - bool result1 = await publish1SyncSource.Task; - Assert.True(result1, $"Non concurrent dispatch lead to deadlock after {maximumWaitTime}"); - - bool result2 = await publish1SyncSource.Task; - Assert.True(result2, $"Non concurrent dispatch lead to deadlock after {maximumWaitTime}"); - } - } - - [Fact] - public async Task TestBasicRejectAsync() - { - var s = new SemaphoreSlim(0, 1); - var cf = new ConnectionFactory { DispatchConsumersAsync = true }; - using IConnection connection = cf.CreateConnection(); - using IChannel channel = connection.CreateChannel(); - - var consumer = new AsyncEventingBasicConsumer(channel); - consumer.Received += async (object sender, BasicDeliverEventArgs args) => - { - var c = sender as AsyncEventingBasicConsumer; - Assert.NotNull(c); - await channel.BasicCancelAsync(c.ConsumerTags[0]); - await channel.BasicRejectAsync(args.DeliveryTag, true); - s.Release(1); - }; - - QueueDeclareOk q = await channel.QueueDeclareAsync(string.Empty, false, false, true, false, null); - string queueName = q.QueueName; - const string publish1 = "sync-hi-1"; - byte[] body = Encoding.UTF8.GetBytes(publish1); - await channel.BasicPublishAsync(string.Empty, queueName, body); - - await channel.BasicConsumeAsync(queue: queueName, autoAck: false, - consumerTag: string.Empty, noLocal: false, exclusive: false, - arguments: null, consumer); - - await s.WaitAsync(); - - uint messageCount, consumerCount = 0; - ushort tries = 5; - do - { - QueueDeclareOk result = await channel.QueueDeclareAsync(queue: queueName, passive: true, false, false, false, null); - consumerCount = result.ConsumerCount; - messageCount = result.MessageCount; - if (consumerCount == 0 && messageCount > 0) - { - break; - } - else - { - await Task.Delay(500); - } - } while (tries-- > 0); - - if (tries == 0) - { - Assert.Fail("[ERROR] failed waiting for MessageCount > 0 && ConsumerCount == 0"); - } - else - { - Assert.Equal((uint)1, messageCount); - Assert.Equal((uint)0, consumerCount); - } - } - - [Fact] - public async Task TestBasicAckAsync() - { - int messageCount = 1024; - var s = new SemaphoreSlim(0, 1); - var cf = new ConnectionFactory { DispatchConsumersAsync = true }; - using IConnection connection = cf.CreateConnection(); - using IChannel channel = connection.CreateChannel(); - - var consumer = new AsyncEventingBasicConsumer(channel); - consumer.Received += async (object sender, BasicDeliverEventArgs args) => - { - var c = sender as AsyncEventingBasicConsumer; - Assert.NotNull(c); - await channel.BasicAckAsync(args.DeliveryTag, false); - --messageCount; - if (messageCount == 0) - { - s.Release(1); - } - }; - - QueueDeclareOk q = await channel.QueueDeclareAsync(string.Empty, false, false, true, false, null); - string queueName = q.QueueName; - - await channel.BasicQosAsync(0, 1, false); - await channel.BasicConsumeAsync(queue: queueName, autoAck: false, - consumerTag: string.Empty, noLocal: false, exclusive: false, - arguments: null, consumer); - - var publishTask = Task.Run(async () => - { - for (int i = 0; i < messageCount; i++) - { - byte[] body = Encoding.UTF8.GetBytes(Guid.NewGuid().ToString()); - await channel.BasicPublishAsync(string.Empty, queueName, body); - } - }); - - await s.WaitAsync(); - await publishTask; - - Assert.Equal(0, messageCount); - } - } -} diff --git a/projects/Unit/TestFloodPublishing.cs b/projects/Unit/TestFloodPublishing.cs deleted file mode 100644 index e541e5399c..0000000000 --- a/projects/Unit/TestFloodPublishing.cs +++ /dev/null @@ -1,166 +0,0 @@ -// This source code is dual-licensed under the Apache License, version -// 2.0, and the Mozilla Public License, version 2.0. -// -// The APL v2.0: -// -//--------------------------------------------------------------------------- -// Copyright (c) 2007-2020 VMware, Inc. -// -// 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 -// -// https://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. -//--------------------------------------------------------------------------- -// -// The MPL v2.0: -// -//--------------------------------------------------------------------------- -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. -// -// Copyright (c) 2007-2020 VMware, Inc. All rights reserved. -//--------------------------------------------------------------------------- - -using System; -using System.Diagnostics; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using RabbitMQ.Client.Events; -using Xunit; -using Xunit.Abstractions; - -namespace RabbitMQ.Client.Unit -{ - [Collection("IntegrationFixture")] - public class TestFloodPublishing - { - private readonly ITestOutputHelper _output; - private readonly byte[] _body = new byte[2048]; - private readonly TimeSpan _tenSeconds = TimeSpan.FromSeconds(10); - - public TestFloodPublishing(ITestOutputHelper output) - { - _output = output; - } - - [Fact] - public async Task TestUnthrottledFloodPublishingAsync() - { - var connFactory = new ConnectionFactory() - { - RequestedHeartbeat = TimeSpan.FromSeconds(60), - AutomaticRecoveryEnabled = false - }; - - var closeWatch = new Stopwatch(); - using (IConnection conn = connFactory.CreateConnection()) - { - using (IChannel channel = conn.CreateChannel()) - { - conn.ConnectionShutdown += (_, args) => - { - if (args.Initiator != ShutdownInitiator.Application) - { - Assert.Fail("Unexpected connection shutdown!"); - } - }; - - var stopwatch = Stopwatch.StartNew(); - int i = 0; - try - { - for (i = 0; i < 65535 * 64; i++) - { - if (i % 65536 == 0) - { - if (stopwatch.Elapsed > _tenSeconds) - { - break; - } - } - - await channel.BasicPublishAsync(CachedString.Empty, CachedString.Empty, _body); - } - } - finally - { - stopwatch.Stop(); - _output.WriteLine($"sent {i}, done in {stopwatch.Elapsed.TotalMilliseconds} ms"); - } - - Assert.True(conn.IsOpen); - closeWatch.Start(); - } - } - closeWatch.Stop(); - _output.WriteLine($"Closing took {closeWatch.Elapsed.TotalMilliseconds} ms"); - } - - [Fact] - public async Task TestMultithreadFloodPublishingAsync() - { - string message = "Hello from test TestMultithreadFloodPublishing"; - byte[] sendBody = Encoding.UTF8.GetBytes(message); - int publishCount = 4096; - int receivedCount = 0; - AutoResetEvent autoResetEvent = new AutoResetEvent(false); - - var cf = new ConnectionFactory() - { - RequestedHeartbeat = TimeSpan.FromSeconds(60), - AutomaticRecoveryEnabled = false - }; - - using (IConnection c = cf.CreateConnection()) - { - string queueName = null; - using (IChannel m = c.CreateChannel()) - { - QueueDeclareOk q = m.QueueDeclare(); - queueName = q.QueueName; - } - - Task pub = Task.Run(async () => - { - using (IChannel pubCh = c.CreateChannel()) - { - for (int i = 0; i < publishCount; i++) - { - await pubCh.BasicPublishAsync(string.Empty, queueName, sendBody); - } - } - }); - - using (IChannel consumeCh = c.CreateChannel()) - { - var consumer = new EventingBasicConsumer(consumeCh); - consumer.Received += (o, a) => - { - string receivedMessage = Encoding.UTF8.GetString(a.Body.ToArray()); - Assert.Equal(message, receivedMessage); - Interlocked.Increment(ref receivedCount); - if (receivedCount == publishCount) - { - autoResetEvent.Set(); - } - }; - consumeCh.BasicConsume(queueName, true, consumer); - - Assert.True(autoResetEvent.WaitOne(_tenSeconds)); - } - - await pub; - Assert.Equal(publishCount, receivedCount); - } - } - } -} diff --git a/projects/Unit/TestRecoverAfterCancel.cs b/projects/Unit/TestRecoverAfterCancel.cs deleted file mode 100644 index 9342a4a7bd..0000000000 --- a/projects/Unit/TestRecoverAfterCancel.cs +++ /dev/null @@ -1,109 +0,0 @@ -// This source code is dual-licensed under the Apache License, version -// 2.0, and the Mozilla Public License, version 2.0. -// -// The APL v2.0: -// -//--------------------------------------------------------------------------- -// Copyright (c) 2007-2020 VMware, Inc. -// -// 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 -// -// https://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. -//--------------------------------------------------------------------------- -// -// The MPL v2.0: -// -//--------------------------------------------------------------------------- -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. -// -// Copyright (c) 2007-2020 VMware, Inc. All rights reserved. -//--------------------------------------------------------------------------- - -using System; -using System.Collections.Concurrent; -using System.Text; -using RabbitMQ.Client.Events; -using RabbitMQ.Client.Impl; -using Xunit; - -#pragma warning disable 0618 - -namespace RabbitMQ.Client.Unit -{ - [Collection("IntegrationFixture")] - public class TestRecoverAfterCancel : IDisposable - { - IConnection _connection; - IChannel _channel; - string _queue; - int _callbackCount; - - public TestRecoverAfterCancel() - { - _connection = new ConnectionFactory().CreateConnection(); - _channel = _connection.CreateChannel(); - _queue = _channel.QueueDeclare("", false, true, false, null); - } - - public int ChannelNumber(IChannel channel) - { - return ((ChannelBase)channel).Session.ChannelNumber; - } - - public void Dispose() - { - _connection.Abort(); - } - - [Fact] - public void TestRecoverAfterCancel_() - { - UTF8Encoding enc = new UTF8Encoding(); - _channel.BasicPublish("", _queue, enc.GetBytes("message")); - EventingBasicConsumer Consumer = new EventingBasicConsumer(_channel); - BlockingCollection<(bool Redelivered, byte[] Body)> EventQueue = new BlockingCollection<(bool Redelivered, byte[] Body)>(); - // Making sure we copy the delivery body since it could be disposed at any time. - Consumer.Received += (_, e) => EventQueue.Add((e.Redelivered, e.Body.ToArray())); - - string CTag = _channel.BasicConsume(_queue, false, Consumer); - (bool Redelivered, byte[] Body) Event = EventQueue.Take(); - _channel.BasicCancel(CTag); - _channel.BasicRecover(true); - - EventingBasicConsumer Consumer2 = new EventingBasicConsumer(_channel); - BlockingCollection<(bool Redelivered, byte[] Body)> EventQueue2 = new BlockingCollection<(bool Redelivered, byte[] Body)>(); - // Making sure we copy the delivery body since it could be disposed at any time. - Consumer2.Received += (_, e) => EventQueue2.Add((e.Redelivered, e.Body.ToArray())); - _channel.BasicConsume(_queue, false, Consumer2); - (bool Redelivered, byte[] Body) Event2 = EventQueue2.Take(); - - Assert.Equal(Event.Body, Event2.Body); - Assert.False(Event.Redelivered); - Assert.True(Event2.Redelivered); - } - - [Fact] - public void TestRecoverCallback() - { - _callbackCount = 0; - _channel.BasicRecoverOk += IncrCallback; - _channel.BasicRecover(true); - Assert.Equal(1, _callbackCount); - } - - void IncrCallback(object sender, EventArgs args) - { - _callbackCount++; - } - } -}