From cd401f93fabdd4952fe7e1d8c2bfdf64d35e9769 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 1 Jul 2025 22:04:43 +1200 Subject: [PATCH 1/3] Remove unused outer server task. --- lib/sus/fixtures/async/http/server_context.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/sus/fixtures/async/http/server_context.rb b/lib/sus/fixtures/async/http/server_context.rb index 5bff6b2..af3df2e 100644 --- a/lib/sus/fixtures/async/http/server_context.rb +++ b/lib/sus/fixtures/async/http/server_context.rb @@ -115,9 +115,7 @@ def before @server = make_server(@server_endpoint) - @server_task = Async do - @server.run - end + @server_task = @server.run @client_endpoint = make_client_endpoint(@bound_endpoint) mock(@client_endpoint) do |wrapper| @@ -135,6 +133,7 @@ def after(error = nil) ::Async::Task.current.with_timeout(1) do @client&.close @server_task&.stop + @server_task&.wait @bound_endpoint&.close end From 941182e59282fe9226eeb612765bddc763d570c0 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 1 Jul 2025 22:06:08 +1200 Subject: [PATCH 2/3] Modernize code. --- .github/workflows/documentation-coverage.yaml | 2 +- .github/workflows/documentation.yaml | 2 +- .github/workflows/rubocop.yaml | 20 ++++--------------- .github/workflows/test-coverage.yaml | 12 ++++++----- .github/workflows/test-external.yaml | 2 +- .github/workflows/test.yaml | 2 +- .rubocop.yml | 10 ++++++++++ config/sus.rb | 2 +- lib/sus/fixtures/async/http.rb | 4 ++-- lib/sus/fixtures/async/http/server_context.rb | 12 +++++------ license.md | 2 +- sus-fixtures-async-http.gemspec | 8 ++++---- test/sus/fixtures/async/http.rb | 4 ++-- .../sus/fixtures/async/http/server_context.rb | 14 ++++++------- 14 files changed, 48 insertions(+), 48 deletions(-) diff --git a/.github/workflows/documentation-coverage.yaml b/.github/workflows/documentation-coverage.yaml index b3bac9a..8d801c5 100644 --- a/.github/workflows/documentation-coverage.yaml +++ b/.github/workflows/documentation-coverage.yaml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: - ruby-version: "3.3" + ruby-version: "3.4" bundler-cache: true - name: Validate coverage diff --git a/.github/workflows/documentation.yaml b/.github/workflows/documentation.yaml index f5f553a..e47c6b3 100644 --- a/.github/workflows/documentation.yaml +++ b/.github/workflows/documentation.yaml @@ -29,7 +29,7 @@ jobs: - uses: ruby/setup-ruby@v1 with: - ruby-version: "3.3" + ruby-version: "3.4" bundler-cache: true - name: Installing packages diff --git a/.github/workflows/rubocop.yaml b/.github/workflows/rubocop.yaml index edfd9bc..287c06d 100644 --- a/.github/workflows/rubocop.yaml +++ b/.github/workflows/rubocop.yaml @@ -1,4 +1,4 @@ -name: Test External +name: RuboCop on: [push, pull_request] @@ -9,26 +9,14 @@ env: CONSOLE_OUTPUT: XTerm jobs: - test: - name: ${{matrix.ruby}} on ${{matrix.os}} - runs-on: ${{matrix.os}}-latest - - strategy: - matrix: - os: - - ubuntu - - macos - - ruby: - - "3.1" - - "3.2" - - "3.3" + check: + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: - ruby-version: ${{matrix.ruby}} + ruby-version: ruby bundler-cache: true - name: Run RuboCop diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index d7e5b24..e6dc5c3 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -21,7 +21,7 @@ jobs: - macos ruby: - - "3.3" + - "3.4" steps: - uses: actions/checkout@v4 @@ -33,9 +33,11 @@ jobs: - name: Run tests timeout-minutes: 5 run: bundle exec bake test - - - uses: actions/upload-artifact@v3 + + - uses: actions/upload-artifact@v4 with: + include-hidden-files: true + if-no-files-found: error name: coverage-${{matrix.os}}-${{matrix.ruby}} path: .covered.db @@ -47,10 +49,10 @@ jobs: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: - ruby-version: "3.3" + ruby-version: "3.4" bundler-cache: true - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 - name: Validate coverage timeout-minutes: 5 diff --git a/.github/workflows/test-external.yaml b/.github/workflows/test-external.yaml index 21898f5..217a86a 100644 --- a/.github/workflows/test-external.yaml +++ b/.github/workflows/test-external.yaml @@ -20,9 +20,9 @@ jobs: - macos ruby: - - "3.1" - "3.2" - "3.3" + - "3.4" steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 0769a98..6810b59 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -21,9 +21,9 @@ jobs: - macos ruby: - - "3.1" - "3.2" - "3.3" + - "3.4" experimental: [false] diff --git a/.rubocop.yml b/.rubocop.yml index 442c667..573c239 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -16,6 +16,9 @@ Layout/IndentationConsistency: Enabled: true EnforcedStyle: normal +Layout/BlockAlignment: + Enabled: true + Layout/EndAlignment: Enabled: true EnforcedStyleAlignWith: start_of_line @@ -42,5 +45,12 @@ Layout/EmptyLinesAroundClassBody: Layout/EmptyLinesAroundModuleBody: Enabled: true +Layout/EmptyLineAfterMagicComment: + Enabled: true + Style/FrozenStringLiteralComment: Enabled: true + +Style/StringLiterals: + Enabled: true + EnforcedStyle: double_quotes diff --git a/config/sus.rb b/config/sus.rb index 24cb323..0aa98ac 100644 --- a/config/sus.rb +++ b/config/sus.rb @@ -3,5 +3,5 @@ # Released under the MIT License. # Copyright, 2022-2023, by Samuel Williams. -require 'covered/sus' +require "covered/sus" include Covered::Sus diff --git a/lib/sus/fixtures/async/http.rb b/lib/sus/fixtures/async/http.rb index 06983b1..0c516bf 100644 --- a/lib/sus/fixtures/async/http.rb +++ b/lib/sus/fixtures/async/http.rb @@ -3,5 +3,5 @@ # Released under the MIT License. # Copyright, 2022-2023, by Samuel Williams. -require_relative 'http/server_context' -require_relative 'http/version' +require_relative "http/server_context" +require_relative "http/version" diff --git a/lib/sus/fixtures/async/http/server_context.rb b/lib/sus/fixtures/async/http/server_context.rb index af3df2e..ce65fc4 100644 --- a/lib/sus/fixtures/async/http/server_context.rb +++ b/lib/sus/fixtures/async/http/server_context.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2022-2024, by Samuel Williams. +# Copyright, 2022-2025, by Samuel Williams. -require 'sus/fixtures/async/reactor_context' +require "sus/fixtures/async/reactor_context" -require 'async/http/server' -require 'async/http/client' -require 'async/http/endpoint' +require "async/http/server" +require "async/http/client" +require "async/http/endpoint" module Sus::Fixtures module Async @@ -20,7 +20,7 @@ def protocol end def url - 'http://localhost:0' + "http://localhost:0" end def bound_urls diff --git a/license.md b/license.md index 76e848d..7a818cf 100644 --- a/license.md +++ b/license.md @@ -1,6 +1,6 @@ # MIT License -Copyright, 2022-2024, by Samuel Williams. +Copyright, 2022-2025, by Samuel Williams. Copyright, 2023, by Felix Yan. Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/sus-fixtures-async-http.gemspec b/sus-fixtures-async-http.gemspec index 3b7592e..4f26f74 100644 --- a/sus-fixtures-async-http.gemspec +++ b/sus-fixtures-async-http.gemspec @@ -10,8 +10,8 @@ Gem::Specification.new do |spec| spec.authors = ["Samuel Williams", "Felix Yan"] spec.license = "MIT" - spec.cert_chain = ['release.cert'] - spec.signing_key = File.expand_path('~/.gem/release.pem') + spec.cert_chain = ["release.cert"] + spec.signing_key = File.expand_path("~/.gem/release.pem") spec.homepage = "https://github.com/suspecting/sus-fixtures-async-http" @@ -21,9 +21,9 @@ Gem::Specification.new do |spec| "source_code_uri" => "https://github.com/suspecting/sus-fixtures-async-http.git", } - spec.files = Dir.glob(['{lib}/**/*', '*.md'], File::FNM_DOTMATCH, base: __dir__) + spec.files = Dir.glob(["{lib}/**/*", "*.md"], File::FNM_DOTMATCH, base: __dir__) - spec.required_ruby_version = ">= 3.1" + spec.required_ruby_version = ">= 3.2" spec.add_dependency "async-http", "~> 0.54" spec.add_dependency "sus", "~> 0.31" diff --git a/test/sus/fixtures/async/http.rb b/test/sus/fixtures/async/http.rb index 9f02625..705270b 100644 --- a/test/sus/fixtures/async/http.rb +++ b/test/sus/fixtures/async/http.rb @@ -3,10 +3,10 @@ # Released under the MIT License. # Copyright, 2022-2023, by Samuel Williams. -require 'sus/fixtures/async/http' +require "sus/fixtures/async/http" describe Sus::Fixtures::Async::HTTP::VERSION do - it 'is a version string' do + it "is a version string" do expect(subject).to be =~ /\d+\.\d+\.\d+/ end end diff --git a/test/sus/fixtures/async/http/server_context.rb b/test/sus/fixtures/async/http/server_context.rb index 08a1e70..4e17d2f 100644 --- a/test/sus/fixtures/async/http/server_context.rb +++ b/test/sus/fixtures/async/http/server_context.rb @@ -3,31 +3,31 @@ # Released under the MIT License. # Copyright, 2022-2024, by Samuel Williams. -require 'sus/fixtures/async/http/server_context' +require "sus/fixtures/async/http/server_context" describe Sus::Fixtures::Async::HTTP::ServerContext do include Sus::Fixtures::Async::HTTP::ServerContext let(:response) {client.get("/")} - it 'can perform a reqeust' do + it "can perform a request" do expect(response.read).to be == "Hello World!" end - it 'has a timeout' do + it "has a timeout" do expect(timeout).to(be > 0).and(be <= 60) end - it 'has a bound url' do + it "has a bound url" do expect(bound_url).to be =~ %r{http://127.0.0.1} end - it 'has a server' do + it "has a server" do expect(server).to be_a(::Async::HTTP::Server) end - with '#client_endpoint' do - it 'is suitable as an HTTP endpoint' do + with "#client_endpoint" do + it "is suitable as an HTTP endpoint" do expect(client_endpoint).not.to be_nil expect(client_endpoint).to have_attributes( protocol: be == endpoint.protocol, From 6cd2f7c66445520f552d2d0d9a6e8c0a3b3784f7 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 1 Jul 2025 22:13:13 +1200 Subject: [PATCH 3/3] 100% documentation coverage. --- lib/sus/fixtures/async/http.rb | 13 +++++ lib/sus/fixtures/async/http/server_context.rb | 52 +++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/lib/sus/fixtures/async/http.rb b/lib/sus/fixtures/async/http.rb index 0c516bf..7ca772c 100644 --- a/lib/sus/fixtures/async/http.rb +++ b/lib/sus/fixtures/async/http.rb @@ -3,5 +3,18 @@ # Released under the MIT License. # Copyright, 2022-2023, by Samuel Williams. +# @namespace +module Sus + # @namespace + module Fixtures + # @namespace + module Async + # @namespace + module HTTP + end + end + end +end + require_relative "http/server_context" require_relative "http/version" diff --git a/lib/sus/fixtures/async/http/server_context.rb b/lib/sus/fixtures/async/http/server_context.rb index ce65fc4..5faad96 100644 --- a/lib/sus/fixtures/async/http/server_context.rb +++ b/lib/sus/fixtures/async/http/server_context.rb @@ -12,17 +12,33 @@ module Sus::Fixtures module Async module HTTP + # ServerContext provides a test fixture for HTTP server and client testing. + # It sets up an HTTP server with a bound endpoint and provides a corresponding client + # for making requests. This context handles the lifecycle of both server and client, + # ensuring proper setup and teardown. + # + # The context automatically: + # - Binds to an available port + # - Starts an HTTP server with configurable middleware + # - Creates a client configured to connect to the server + # - Handles cleanup of resources after tests module ServerContext include ReactorContext + # The HTTP protocol version to use for the server. + # @returns [Class] The protocol class (defaults to HTTP/1.1). def protocol ::Async::HTTP::Protocol::HTTP1 end + # The base URL for the HTTP server. + # @returns [String] The URL string with host and port (defaults to localhost:0 for auto-assigned port). def url "http://localhost:0" end + # Get all bound URLs for the server endpoints. + # @returns [Array(String)] Array of URLs the server is bound to, sorted alphabetically. def bound_urls urls = [] @@ -44,61 +60,94 @@ def bound_urls return urls end + # Get the first bound URL for the server. + # @returns [String] The first bound URL, typically used for single-endpoint testing. def bound_url bound_urls.first end + # Options for configuring the HTTP endpoint. + # @returns [Hash] Hash of endpoint options including port reuse and protocol settings. def endpoint_options {reuse_port: true, protocol: protocol} end + # The HTTP endpoint configuration. + # @returns [Async::HTTP::Endpoint] Parsed endpoint with configured options. def endpoint ::Async::HTTP::Endpoint.parse(url, **endpoint_options) end + # Number of retries for client requests. + # @returns [Integer] Number of retry attempts (defaults to 1). def retries 1 end + # The HTTP application/middleware to serve. + # @returns [Object] The application object that handles HTTP requests (defaults to HelloWorld middleware). def app ::Protocol::HTTP::Middleware::HelloWorld end + # The middleware stack for the HTTP server. + # @returns [Object] The middleware configuration (defaults to the app). def middleware app end + # Create the server endpoint from a bound endpoint. + # @parameter bound_endpoint [Async::HTTP::Endpoint] The bound endpoint. + # @returns [Async::HTTP::Endpoint] The server endpoint configuration. def make_server_endpoint(bound_endpoint) bound_endpoint end + # Create an HTTP server instance. + # @parameter endpoint [Async::HTTP::Endpoint] The endpoint to bind the server to. + # @returns [Async::HTTP::Server] The configured HTTP server. def make_server(endpoint) ::Async::HTTP::Server.new(middleware, endpoint) end + # The HTTP server instance. + # @returns [Async::HTTP::Server] The running HTTP server. def server @server end + # The HTTP client instance. + # @returns [Async::HTTP::Client] The HTTP client configured to connect to the server. def client @client end + # The client endpoint configuration. + # @returns [Async::HTTP::Endpoint] The endpoint the client uses to connect to the server. def client_endpoint @client_endpoint end + # Create a client endpoint from a bound endpoint. + # @parameter bound_endpoint [Async::HTTP::Endpoint] The bound server endpoint. + # @returns [Async::HTTP::Endpoint] The client endpoint with local address and timeout. def make_client_endpoint(bound_endpoint) # Pass through the timeout: bound_endpoint.local_address_endpoint(timeout: endpoint.timeout) end + # Create an HTTP client instance. + # @parameter endpoint [Async::HTTP::Endpoint] The endpoint to connect to. + # @parameter options [Hash] Additional client options. + # @returns [Async::HTTP::Client] The configured HTTP client. def make_client(endpoint, **options) options[:retries] = retries unless options.key?(:retries) ::Async::HTTP::Client.new(endpoint, **options) end + # Set up the server and client before running tests. + # This method binds the endpoint, starts the server, and creates a client. def before super @@ -128,6 +177,9 @@ def before @client = make_client(@client_endpoint) end + # Clean up resources after running tests. + # This method closes the client, stops the server, and closes the bound endpoint. + # @parameter error [Exception | Nil] Any error that occurred during the test. def after(error = nil) # We add a timeout here, to avoid hanging in `@client.close`: ::Async::Task.current.with_timeout(1) do