From 2c5c1e1dee87c9b4b7f8d3347a30979a856567a1 Mon Sep 17 00:00:00 2001 From: Luke Bakken Date: Wed, 18 Oct 2023 08:30:23 -0700 Subject: [PATCH] Fix test by using messagesReceived and messageCount Add ConfirmSelectAsync Add BasicNackAsync and fix small bug in ConfirmSelectAsync Add TestBasicGetAsync Implement CloseAsync Doc blocks and simplify return thanks to @Tornhoof No need to set an exception when a channel shutdown was explicitly requested Add QueuePurgeAsync Fix sync Channel Close and add test using CloseAsync() Add QueueUnbindAsync Add async Tx methods Remove BasicRecover and BasicRecoverAsync since they are both deprecated. Separate out Unit, Integration and Parallel Integration tests * Creates dedicated test projects for parallel test execution (Integration.csproj) and sequential (SequentialIntegration.csproj). * Ensures that the ThreadPool is set with enough threads. * Ensures that all test connections have their client provided name set. * Fix SequentialTests that require a unique connection name. Fix OAuth2 test runners --- .ci/oauth2/setup.sh | 10 +- .ci/oauth2/test.sh | 2 +- .ci/ubuntu/rabbitmq.conf | 5 +- .ci/windows/gha-run-tests.ps1 | 39 -- .ci/windows/gha-setup.ps1 | 14 +- .ci/windows/rabbitmq.conf.in | 5 +- .git-blame-ignore-revs | 5 + .github/workflows/build-test.yaml | 182 ++++- .gitignore | 6 +- Build.csproj | 11 +- RUNNING_TESTS.md | 6 +- RabbitMQDotNetClient.sln | 42 +- build.ps1 | 2 +- .../RabbitMQ.Client/RabbitMQ.Client.csproj | 9 + .../client/api/AmqpTimestamp.cs | 3 - .../RabbitMQ.Client/client/api/IChannel.cs | 145 ++-- .../client/api/IChannelExtensions.cs | 23 +- .../RabbitMQ.Client/client/framing/Channel.cs | 26 +- .../client/impl/AsyncRpcContinuations.cs | 135 +++- .../client/impl/AutorecoveringChannel.cs | 108 +-- .../client/impl/AutorecoveringConnection.cs | 5 +- .../client/impl/ChannelBase.cs | 344 +++++++-- .../client/impl/Connection.Receive.cs | 2 + .../client/impl/RecoveryAwareChannel.cs | 25 +- .../client/impl/SocketFrameHandler.cs | 6 +- .../CreateChannel/CreateChannel.csproj | 2 +- .../Applications}/CreateChannel/Program.cs | 0 .../MassPublish/MassPublish.csproj | 2 +- .../Applications}/MassPublish/Program.cs | 0 projects/Test/Common/Common.csproj | 38 + projects/Test/Common/IntegrationFixture.cs | 282 ++++++++ projects/{Unit => Test/Common}/RabbitMQCtl.cs | 243 +++---- .../Common/TimingFixture.cs} | 31 +- projects/Test/Integration/Integration.csproj | 57 ++ projects/{Unit => Test/Integration}/SslEnv.cs | 25 +- .../Test/Integration/TestAsyncConsumer.cs | 316 +++++++++ .../TestAsyncConsumerExceptions.cs | 21 +- .../{Unit => Test/Integration}/TestAuth.cs | 25 +- .../Integration}/TestBasicGet.cs | 57 +- projects/Test/Integration/TestBasicPublish.cs | 333 +++++++++ .../Integration}/TestChannelAllocation.cs | 76 +- .../Integration}/TestChannelShutdown.cs | 5 +- .../Integration}/TestChannelSoftErrors.cs | 3 +- ...estConcurrentAccessWithSharedConnection.cs | 268 +++++++ .../Integration}/TestConfirmSelect.cs | 3 +- .../Integration}/TestConnectionFactory.cs | 183 ++--- ...estConnectionFactoryContinuationTimeout.cs | 11 +- .../Integration}/TestConnectionShutdown.cs | 28 +- projects/Test/Integration/TestConsumer.cs | 297 ++++++++ .../Integration}/TestConsumerCancelNotify.cs | 3 +- .../Integration}/TestConsumerCount.cs | 3 +- .../Integration}/TestConsumerExceptions.cs | 3 +- .../TestConsumerOperationDispatch.cs | 43 +- .../Integration}/TestEventingConsumer.cs | 7 +- .../Integration}/TestExceptionMessages.cs | 2 +- .../Integration}/TestExchangeDeclare.cs | 3 +- .../Integration}/TestExtensions.cs | 7 +- .../Test/Integration/TestFloodPublishing.cs | 148 ++++ .../Integration}/TestHeartbeats.cs | 112 +-- .../Integration}/TestInitialConnection.cs | 7 +- .../Integration}/TestInvalidAck.cs | 3 +- .../Integration}/TestMainLoop.cs | 20 +- .../Integration}/TestMessageCount.cs | 3 +- .../{Unit => Test/Integration}/TestNowait.cs | 3 +- .../Integration}/TestPassiveDeclare.cs | 2 +- .../Integration}/TestPublishSharedChannel.cs | 30 +- .../Integration}/TestPublisherConfirms.cs | 69 +- .../Integration}/TestQueueDeclare.cs | 5 +- .../{Unit => Test/Integration}/TestSsl.cs | 63 +- .../Integration}/TestUpdateSecret.cs | 3 +- projects/Test/Integration/TimingFixture.cs | 46 ++ .../OAuth2}/APIApproval.Approve.verified.txt | 0 .../OAuth2}/APIApproval.cs | 0 .../OAuth2/OAuth2.csproj} | 12 +- .../{OAuth2Test => Test/OAuth2}/README.md | 0 .../OAuth2}/RequestFormMatcher.cs | 0 .../{OAuth2Test => Test/OAuth2}/TestOAuth2.cs | 3 +- .../OAuth2}/TestOAuth2Client.cs | 0 .../TestOAuth2ClientCredentialsProvider.cs | 0 .../OAuth2}/enabled_plugins | 0 .../OAuth2}/keycloak/import/test-realm.json | 0 .../OAuth2}/keycloak/rabbitmq.conf | 0 .../keycloak/signing-key/signing-key.pem | 0 .../OAuth2}/uaa/log4j2.properties | 0 .../OAuth2}/uaa/rabbitmq.conf | 0 .../OAuth2}/uaa/signing-key/signing-key.pem | 0 .../{OAuth2Test => Test/OAuth2}/uaa/uaa.yml | 0 .../SequentialIntegration.csproj | 51 ++ .../SequentialIntegrationFixture.cs | 89 +++ .../TestConnectionBlocked.cs | 6 +- .../TestConnectionRecovery.cs | 656 ++---------------- .../TestConnectionRecoveryBase.cs | 389 +++++++++++ .../TestConnectionRecoveryWithoutSetup.cs | 333 +++++++++ .../Unit/APIApproval.Approve.verified.txt | 18 +- projects/{ => Test}/Unit/APIApproval.cs | 2 +- .../{Unit/Helper => Test/Unit}/DebugUtil.cs | 4 +- .../Unit/TestAmqpTcpEndpointParsing.cs | 3 +- projects/{ => Test}/Unit/TestAmqpUri.cs | 3 +- .../{ => Test}/Unit/TestBasicProperties.cs | 8 +- projects/{ => Test}/Unit/TestBlockingCell.cs | 2 +- .../{ => Test}/Unit/TestContentHeaderCodec.cs | 3 +- .../Unit/TestFieldTableFormatting.cs | 4 +- .../Unit/TestFieldTableFormattingGeneric.cs | 4 +- .../{ => Test}/Unit/TestFrameFormatting.cs | 23 +- .../Unit/TestIEndpointResolverExtensions.cs | 3 +- projects/{ => Test}/Unit/TestIntAllocator.cs | 2 +- .../Unit/TestMethodArgumentCodec.cs | 3 +- .../Unit/TestNetworkByteOrderSerialization.cs | 3 +- .../{ => Test}/Unit/TestPublicationAddress.cs | 3 +- .../Unit/TestRpcContinuationQueue.cs | 2 +- .../{ => Test}/Unit/TestTcpClientAdapter.cs | 2 +- .../Unit/TestTimerBasedCredentialRefresher.cs | 3 +- .../Unit/TimingFixture.cs} | 31 +- projects/{ => Test}/Unit/Unit.csproj | 11 +- .../{ => Test}/Unit/WireFormattingFixture.cs | 2 +- projects/Unit/Fixtures.cs | 514 -------------- projects/Unit/TestAsyncConsumer.cs | 358 ---------- projects/Unit/TestBasicPublish.cs | 296 -------- ...estConcurrentAccessWithSharedConnection.cs | 223 ------ projects/Unit/TestConsumer.cs | 204 ------ projects/Unit/TestFloodPublishing.cs | 166 ----- projects/Unit/TestRecoverAfterCancel.cs | 109 --- 122 files changed, 4231 insertions(+), 3368 deletions(-) delete mode 100644 .ci/windows/gha-run-tests.ps1 create mode 100644 .git-blame-ignore-revs rename projects/{TestApplications => Test/Applications}/CreateChannel/CreateChannel.csproj (83%) rename projects/{TestApplications => Test/Applications}/CreateChannel/Program.cs (100%) rename projects/{TestApplications => Test/Applications}/MassPublish/MassPublish.csproj (83%) rename projects/{TestApplications => Test/Applications}/MassPublish/Program.cs (100%) create mode 100644 projects/Test/Common/Common.csproj create mode 100644 projects/Test/Common/IntegrationFixture.cs rename projects/{Unit => Test/Common}/RabbitMQCtl.cs (67%) rename projects/{RabbitMQ.Client/client/framing/BasicRecoverAsync.cs => Test/Common/TimingFixture.cs} (67%) create mode 100644 projects/Test/Integration/Integration.csproj rename projects/{Unit => Test/Integration}/SslEnv.cs (84%) create mode 100644 projects/Test/Integration/TestAsyncConsumer.cs rename projects/{Unit => Test/Integration}/TestAsyncConsumerExceptions.cs (93%) rename projects/{Unit => Test/Integration}/TestAuth.cs (78%) rename projects/{Unit => Test/Integration}/TestBasicGet.cs (58%) create mode 100644 projects/Test/Integration/TestBasicPublish.cs rename projects/{Unit => Test/Integration}/TestChannelAllocation.cs (66%) rename projects/{Unit => Test/Integration}/TestChannelShutdown.cs (94%) rename projects/{Unit => Test/Integration}/TestChannelSoftErrors.cs (98%) create mode 100644 projects/Test/Integration/TestConcurrentAccessWithSharedConnection.cs rename projects/{Unit => Test/Integration}/TestConfirmSelect.cs (97%) rename projects/{Unit => Test/Integration}/TestConnectionFactory.cs (66%) rename projects/{Unit => Test/Integration}/TestConnectionFactoryContinuationTimeout.cs (85%) rename projects/{Unit => Test/Integration}/TestConnectionShutdown.cs (84%) create mode 100644 projects/Test/Integration/TestConsumer.cs rename projects/{Unit => Test/Integration}/TestConsumerCancelNotify.cs (99%) rename projects/{Unit => Test/Integration}/TestConsumerCount.cs (97%) rename projects/{Unit => Test/Integration}/TestConsumerExceptions.cs (99%) rename projects/{Unit => Test/Integration}/TestConsumerOperationDispatch.cs (91%) rename projects/{Unit => Test/Integration}/TestEventingConsumer.cs (96%) rename projects/{Unit => Test/Integration}/TestExceptionMessages.cs (98%) rename projects/{Unit => Test/Integration}/TestExchangeDeclare.cs (99%) rename projects/{Unit => Test/Integration}/TestExtensions.cs (93%) create mode 100644 projects/Test/Integration/TestFloodPublishing.cs rename projects/{Unit => Test/Integration}/TestHeartbeats.cs (62%) rename projects/{Unit => Test/Integration}/TestInitialConnection.cs (88%) rename projects/{Unit => Test/Integration}/TestInvalidAck.cs (97%) rename projects/{Unit => Test/Integration}/TestMainLoop.cs (82%) rename projects/{Unit => Test/Integration}/TestMessageCount.cs (97%) rename projects/{Unit => Test/Integration}/TestNowait.cs (98%) rename projects/{Unit => Test/Integration}/TestPassiveDeclare.cs (98%) rename projects/{Unit => Test/Integration}/TestPublishSharedChannel.cs (83%) rename projects/{Unit => Test/Integration}/TestPublisherConfirms.cs (66%) rename projects/{Unit => Test/Integration}/TestQueueDeclare.cs (97%) rename projects/{Unit => Test/Integration}/TestSsl.cs (71%) rename projects/{Unit => Test/Integration}/TestUpdateSecret.cs (95%) create mode 100644 projects/Test/Integration/TimingFixture.cs rename projects/{OAuth2Test => Test/OAuth2}/APIApproval.Approve.verified.txt (100%) rename projects/{OAuth2Test => Test/OAuth2}/APIApproval.cs (100%) rename projects/{OAuth2Test/OAuth2Test.csproj => Test/OAuth2/OAuth2.csproj} (79%) rename projects/{OAuth2Test => Test/OAuth2}/README.md (100%) rename projects/{OAuth2Test => Test/OAuth2}/RequestFormMatcher.cs (100%) rename projects/{OAuth2Test => Test/OAuth2}/TestOAuth2.cs (99%) rename projects/{OAuth2Test => Test/OAuth2}/TestOAuth2Client.cs (100%) rename projects/{OAuth2Test => Test/OAuth2}/TestOAuth2ClientCredentialsProvider.cs (100%) rename projects/{OAuth2Test => Test/OAuth2}/enabled_plugins (100%) rename projects/{OAuth2Test => Test/OAuth2}/keycloak/import/test-realm.json (100%) rename projects/{OAuth2Test => Test/OAuth2}/keycloak/rabbitmq.conf (100%) rename projects/{OAuth2Test => Test/OAuth2}/keycloak/signing-key/signing-key.pem (100%) rename projects/{OAuth2Test => Test/OAuth2}/uaa/log4j2.properties (100%) rename projects/{OAuth2Test => Test/OAuth2}/uaa/rabbitmq.conf (100%) rename projects/{OAuth2Test => Test/OAuth2}/uaa/signing-key/signing-key.pem (100%) rename projects/{OAuth2Test => Test/OAuth2}/uaa/uaa.yml (100%) create mode 100644 projects/Test/SequentialIntegration/SequentialIntegration.csproj create mode 100644 projects/Test/SequentialIntegration/SequentialIntegrationFixture.cs rename projects/{Unit => Test/SequentialIntegration}/TestConnectionBlocked.cs (96%) rename projects/{Unit => Test/SequentialIntegration}/TestConnectionRecovery.cs (67%) create mode 100644 projects/Test/SequentialIntegration/TestConnectionRecoveryBase.cs create mode 100644 projects/Test/SequentialIntegration/TestConnectionRecoveryWithoutSetup.cs rename projects/{ => Test}/Unit/APIApproval.Approve.verified.txt (96%) rename projects/{ => Test}/Unit/APIApproval.cs (98%) rename projects/{Unit/Helper => Test/Unit}/DebugUtil.cs (98%) rename projects/{ => Test}/Unit/TestAmqpTcpEndpointParsing.cs (99%) rename projects/{ => Test}/Unit/TestAmqpUri.cs (99%) rename projects/{ => Test}/Unit/TestBasicProperties.cs (97%) rename projects/{ => Test}/Unit/TestBlockingCell.cs (99%) rename projects/{ => Test}/Unit/TestContentHeaderCodec.cs (99%) rename projects/{ => Test}/Unit/TestFieldTableFormatting.cs (99%) rename projects/{ => Test}/Unit/TestFieldTableFormattingGeneric.cs (99%) rename projects/{ => Test}/Unit/TestFrameFormatting.cs (87%) rename projects/{ => Test}/Unit/TestIEndpointResolverExtensions.cs (98%) rename projects/{ => Test}/Unit/TestIntAllocator.cs (98%) rename projects/{ => Test}/Unit/TestMethodArgumentCodec.cs (99%) rename projects/{ => Test}/Unit/TestNetworkByteOrderSerialization.cs (99%) rename projects/{ => Test}/Unit/TestPublicationAddress.cs (98%) rename projects/{ => Test}/Unit/TestRpcContinuationQueue.cs (98%) rename projects/{ => Test}/Unit/TestTcpClientAdapter.cs (98%) rename projects/{ => Test}/Unit/TestTimerBasedCredentialRefresher.cs (99%) rename projects/{RabbitMQ.Client/client/framing/BasicRecover.cs => Test/Unit/TimingFixture.cs} (67%) rename projects/{ => Test}/Unit/Unit.csproj (79%) rename projects/{ => Test}/Unit/WireFormattingFixture.cs (98%) delete mode 100644 projects/Unit/Fixtures.cs delete mode 100644 projects/Unit/TestAsyncConsumer.cs delete mode 100644 projects/Unit/TestBasicPublish.cs delete mode 100644 projects/Unit/TestConcurrentAccessWithSharedConnection.cs delete mode 100644 projects/Unit/TestConsumer.cs delete mode 100644 projects/Unit/TestFloodPublishing.cs delete mode 100644 projects/Unit/TestRecoverAfterCancel.cs 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++; - } - } -}