Skip to content

Commit

Permalink
Merge pull request #643 from bugsnag/idempotent-previous-uuid
Browse files Browse the repository at this point in the history
Allow the command server to provide the next ID when given the last from the previous scenario
  • Loading branch information
Cawllec authored Apr 8, 2024
2 parents 3444b56 + c875680 commit 8339cde
Show file tree
Hide file tree
Showing 14 changed files with 237 additions and 21 deletions.
4 changes: 4 additions & 0 deletions .buildkite/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ steps:
run: doc-server-tests
- docker-compose#v4.14.0:
run: exit-codes-tests
- docker-compose#v4.14.0:
run: command-workflow-tests
- artifacts#v1.9.0:
upload:
- test/fixtures/payload-helpers/maze_output/**/*
Expand Down Expand Up @@ -193,6 +195,8 @@ steps:
run: doc-server-tests
- docker-compose#v4.14.0:
run: exit-codes-tests
- docker-compose#v4.14.0:
run: command-workflow-tests
env:
RUBY_VERSION: "3"
command: 'bundle exec maze-runner -e features/fixtures'
Expand Down
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
# TBD
# 9.7.0 - 2024/04/08

## Enhancements

- Allow command servlet to respond appropriately when given UUID from previous test [643](https://github.com/bugsnag/maze-runner/pull/643)

## Fixes

Expand Down
8 changes: 4 additions & 4 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ GIT
PATH
remote: .
specs:
bugsnag-maze-runner (9.6.0)
bugsnag-maze-runner (9.7.0)
appium_lib (~> 12.0.0)
appium_lib_core (~> 5.4.0)
bugsnag (~> 6.24)
Expand Down Expand Up @@ -42,7 +42,7 @@ GEM
appium_lib_core (5.4.0)
faye-websocket (~> 0.11.0)
selenium-webdriver (~> 4.2, < 4.6)
bugsnag (6.26.3)
bugsnag (6.26.4)
concurrent-ruby (~> 1.0)
builder (3.2.4)
childprocess (4.1.0)
Expand Down Expand Up @@ -118,15 +118,15 @@ GEM
minitest (5.18.1)
mocha (1.13.0)
multi_test (0.1.2)
nokogiri (1.15.5-x86_64-darwin)
nokogiri (1.15.6-x86_64-darwin)
racc (~> 1.4)
optimist (3.0.1)
os (1.0.1)
power_assert (2.0.3)
props (1.2.0)
iniparser (>= 0.1.0)
racc (1.7.3)
rack (2.2.8.1)
rack (2.2.9)
rake (12.3.3)
redcarpet (3.6.0)
regexp_parser (2.9.0)
Expand Down
12 changes: 12 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,18 @@ services:
volumes:
- ./test/fixtures/payload-helpers/maze_output:/app/maze_output

command-workflow-tests:
build:
context: test/fixtures/command-workflow
args:
BRANCH_NAME:
RUBY_VERSION:
environment:
MAZE_BUGSNAG_API_KEY:
USE_LEGACY_DRIVER:
volumes:
- ./test/fixtures/command-workflow/maze_output:/app/maze_output

http-response-tests:
build:
context: test/fixtures/http-response
Expand Down
2 changes: 1 addition & 1 deletion lib/maze.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# Glues the various parts of MazeRunner together that need to be accessed globally,
# providing an alternative to the proliferation of global variables or singletons.
module Maze
VERSION = '9.6.0'
VERSION = '9.7.0'

class << self
attr_accessor :check, :driver, :internal_hooks, :mode, :start_time, :dynamic_retry, :public_address,
Expand Down
6 changes: 6 additions & 0 deletions lib/maze/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ class Server
DEFAULT_STATUS_CODE = 200

class << self

# Records the previous command UUID sent to the test fixture
#
# @return [String] The UUID of the last command sent
attr_accessor :last_command_uuid

# Sets the response delay generator.
#
# @param generator [Maze::Generator] The new generator
Expand Down
41 changes: 26 additions & 15 deletions lib/maze/servlets/command_servlet.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,7 @@ def do_GET(request, response)

if request.query.empty?
# Non-idempotent mode - return the "current" command
commands = Maze::Server.commands

if commands.size_remaining == 0
response.body = NOOP_COMMAND
response.status = 200
else
command = commands.current
response.body = JSON.pretty_generate(command)
response.status = 200
commands.next
end
send_current_command(response)
else
# Idempotent mode
after_uuid = request.query['after']
Expand All @@ -44,6 +34,21 @@ def do_GET(request, response)
response.header['Access-Control-Allow-Origin'] = '*'
end

def send_current_command(response)
commands = Maze::Server.commands

if commands.size_remaining == 0
response.body = NOOP_COMMAND
response.status = 200
else
command = commands.current
Server.last_command_uuid = command[:uuid]
response.body = JSON.pretty_generate(command)
response.status = 200
commands.next
end
end

def command_after(uuid, response)
commands = Maze::Server.commands
if uuid.empty?
Expand All @@ -52,14 +57,20 @@ def command_after(uuid, response)
index = commands.all.find_index {|command| command[:uuid] == uuid }
end
if index.nil?
msg = "Request invalid - there is no command with a UUID of #{uuid} to follow on from"
$logger.error msg
response.body = msg
response.status = 400
# If the UUID given matches the last UUID sent by the server, we can assume the fixture has failed to reset
if uuid.eql?(Server.last_command_uuid)
send_current_command(response)
else
msg = "Request invalid - there is no command with a UUID of #{uuid} to follow on from"
$logger.error msg
response.body = msg
response.status = 400
end
else
if index + 1 < commands.size_all
# Respond with the next command in the queue
command = commands.get(index + 1)
Server.last_command_uuid = command[:uuid]
command_json = JSON.pretty_generate(command)
response.body = command_json
response.status = 200
Expand Down
6 changes: 6 additions & 0 deletions test/fixtures/command-workflow/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
ARG BRANCH_NAME
ARG RUBY_VERSION
FROM 855461928731.dkr.ecr.us-west-1.amazonaws.com/maze-runner:${BRANCH_NAME}-ci-ruby-${RUBY_VERSION}

COPY . /app
WORKDIR /app
3 changes: 3 additions & 0 deletions test/fixtures/command-workflow/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
source "https://rubygems.org"

gem 'bugsnag-maze-runner', path: '../../..'
87 changes: 87 additions & 0 deletions test/fixtures/command-workflow/features/commands.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
Feature: Tests the command servlet works as expected

Scenario: Commands are received in order
Given I add a command with message "first"
And I add a command with message "second"
Then I run the "bounce_command" test script
And I wait to receive an error
Then the error payload field "command_response.message" equals "first"
And the error payload field "command_response.uuid" is stored as the value "uuid"
And the error payload field "command_response.run_uuid" is stored as the value "run_uuid"
And I discard the oldest error
Then I run the "bounce_command" test script
And I wait to receive an error
Then the error payload field "command_response.message" equals "second"
And the error payload field "command_response.uuid" does not equal the stored value "uuid"
And the error payload field "command_response.run_uuid" equals the stored value "run_uuid"

Scenario: Idempotent commands are sent correctly
Given I generate a series of commands with sequential UUIDs
Then I run the "bounce_idempotent_command" test script with UUID "1"
And I wait to receive an error
Then the error payload field "command_response.message" equals "second"
And the error payload field "command_response.uuid" equals "2"
And I discard the oldest error
Then I run the "bounce_idempotent_command" test script with UUID "2"
And I wait to receive an error
Then the error payload field "command_response.message" equals "third"
And the error payload field "command_response.uuid" equals "3"
And I discard the oldest error
Then I run the "bounce_idempotent_command" test script with UUID "1"
And I wait to receive an error
Then the error payload field "command_response.message" equals "second"
And the error payload field "command_response.uuid" equals "2"

Scenario: A noop is sent when no commands are added
When I run the "bounce_command" test script
And I wait to receive an error
Then the error payload field "command_response.message" equals "No commands queued"
And the error payload field "command_response.action" equals "noop"
And the error payload field "command_status" equals 200

Scenario: An noop is sent when commands run out
Given I add a command with message "first"
And I run the "bounce_command" test script
And I wait to receive an error
Then the error payload field "command_response.message" equals "first"
And I discard the oldest error
Then I run the "bounce_command" test script
And I wait to receive an error
Then the error payload field "command_response.message" equals "No commands queued"
And the error payload field "command_response.action" equals "noop"
And the error payload field "command_status" equals 200

Scenario: An error is sent when the idempotent UUID is incorrect
Given I generate a series of commands with sequential UUIDs
Then I run the "bounce_idempotent_command" test script with UUID "8"
And I wait to receive an error
Then the error payload field "command_response" equals "Request invalid - there is no command with a UUID of 8 to follow on from"
And the error payload field "command_status" equals 400

Scenario: The next command is sent when the previous idempotent UUID is used
Given I generate a series of commands with sequential UUIDs
Then I run the "bounce_command" test script
And I wait to receive an error
Then the error payload field "command_response.message" equals "first"
And the error payload field "command_response.uuid" equals "1"
And I discard the oldest error
Then I run the "bounce_command" test script
And I wait to receive an error
Then the error payload field "command_response.message" equals "second"
And the error payload field "command_response.uuid" equals "2"
And I discard the oldest error
Then I run the "bounce_command" test script
And I wait to receive an error
Then the error payload field "command_response.message" equals "third"
And the error payload field "command_response.uuid" equals "3"
And I discard the oldest error
Then I run the "bounce_command" test script
And I wait to receive an error
Then the error payload field "command_response.message" equals "fourth"
And the error payload field "command_response.uuid" equals "4"
And I discard the oldest error

Then I add a command with message "fifth"
And I run the "bounce_idempotent_command" test script with UUID "4"
And I wait to receive an error
Then the error payload field "command_response.message" equals "fifth"
18 changes: 18 additions & 0 deletions test/fixtures/command-workflow/features/scripts/bounce_command.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

require 'date'
require 'net/http'

http = Net::HTTP.new('localhost', '9339')
command_request = Net::HTTP::Get.new('/command')

command_response = http.request(command_request)

bounce_request = Net::HTTP::Post.new('/notify')
bounce_request['Content-Type'] = 'application/json'
bounce_request.body = %({
"command_response": #{command_response.body},
"command_status": #{command_response.code}
})

http.request(bounce_request)
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true

require 'date'
require 'net/http'
require 'json'

target_uuid = ENV['COMMAND_UUID']

http = Net::HTTP.new('localhost', '9339')
command_request = Net::HTTP::Get.new("/command?after=#{target_uuid}")

command_response = http.request(command_request)

bounce_request = Net::HTTP::Post.new('/notify')
bounce_request['Content-Type'] = 'application/json'

begin
JSON.parse(command_response.body)
bounce_request.body = %({
"command_response": #{command_response.body},
"command_status": #{command_response.code}
})
rescue
bounce_request.body = %({
"command_response": "#{command_response.body}",
"command_status": #{command_response.code}
})
end

http.request(bounce_request)
30 changes: 30 additions & 0 deletions test/fixtures/command-workflow/features/steps/steps.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
When('I add a command with message {string}') do |message|
command = {
message: message
}
Maze::Server.commands.add command
end

When('I generate a series of commands with sequential UUIDs') do
Maze::Server.commands.add({ message: 'first'})
Maze::Server.commands.get(0)[:uuid] = '1'
Maze::Server.commands.add({ message: 'second'})
Maze::Server.commands.get(1)[:uuid] = '2'
Maze::Server.commands.add({ message: 'third'})
Maze::Server.commands.get(2)[:uuid] = '3'
Maze::Server.commands.add({ message: 'fourth'})
Maze::Server.commands.get(3)[:uuid] = '4'
end

When('I run the {string} test script') do |test_script|
steps %Q{
And I run the script "features/scripts/#{test_script}.rb" using ruby synchronously
}
end

When('I run the {string} test script with UUID {string}') do |test_script, uuid|
steps %Q{
When I set environment variable "COMMAND_UUID" to "#{uuid}"
And I run the script "features/scripts/#{test_script}.rb" using ruby synchronously
}
end
5 changes: 5 additions & 0 deletions test/fixtures/command-workflow/features/support/env.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Maze.config.enable_bugsnag = false

BeforeAll do
Maze.config.enforce_bugsnag_integrity = false
end

0 comments on commit 8339cde

Please sign in to comment.