Skip to content

Commit

Permalink
test: add infrastructure for cucumber/aruba features
Browse files Browse the repository at this point in the history
cucumber allows us to express test cases in gherkin
given/when/then syntax and the aruba library lets us
take our features to Kokomo, adding step definitions
for common actions when testing a command-line
application.
  • Loading branch information
davidalpert committed Jan 19, 2023
1 parent 51f73f8 commit 37a7698
Show file tree
Hide file tree
Showing 11 changed files with 343 additions and 0 deletions.
16 changes: 16 additions & 0 deletions Taskfile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,22 @@ tasks:
- goconvey
silent: true

features:
desc: run acceptance tests
deps:
- bundle
- gen
cmds:
- bundle exec cucumber --publish-quiet --tags 'not @wip' --tags 'not @ignore'

features-wip:
desc: run wip acceptance tests
deps:
- bundle
- gen
cmds:
- bundle exec cucumber --publish-quiet --tags '@wip' --tags 'not @ignore'

build:
desc: build
run: once
Expand Down
16 changes: 16 additions & 0 deletions features/aruba_available_steps.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Feature: Available Aruba Steps
# # uncomment me to describe available aruba step phrases
# # see step implementations: https://github.com/cucumber/aruba/blob/master/lib/aruba/cucumber
# # see step documentation: https://relishapp.com/cucumber/aruba/v/0-11-0/docs/getting-started

# @announce-stdout
# Scenario: 'available aruba steps'
# Given an executable named "bin/cli" with:
# """
# #!/bin/bash
# git clone https://github.com/cucumber/aruba.git
# cd aruba
# grep -E "When|Given|Then" lib/aruba/cucumber/*.rb | awk -F ":" '{ $1 = ""; print $0}' |sort
# """
# When I run `bin/cli`
# Then the exit status should be 0
8 changes: 8 additions & 0 deletions features/goconfig/_usage.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Feature: usage
Background:
Given I have installed "goconfig" locally into the path

# @announce-stdout @announce-stderr
Scenario: help
When I run `goconfig`
Then the stdout should show usage
23 changes: 23 additions & 0 deletions features/step_definitions/command_steps.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# frozen_string_literal: true

Then(/(the )?(\w+) from `([^`]*)` should (not )?contain "([^"]*)"/) do |_, channel, cmd, negated, content|
run_command_and_validate_channel(
cmd: cmd,
fail_on_error: true,
channel: channel,
negated: negated,
match_as_regex: false,
content: content
)
end

Then(/(the )?(\w+) from `([^`]*)` should (not )?match "([^"]*)"/) do |_, channel, cmd, negated, content|
run_command_and_validate_channel(
cmd: cmd,
fail_on_error: true,
channel: channel,
negated: negated,
match_as_regex: true,
content: content
)
end
26 changes: 26 additions & 0 deletions features/step_definitions/installation_steps.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
require 'fileutils'

Given('I have installed {string} into {string} within the current directory') do |app, path|
install_binary_and_add_to_path(app, path)
end

Given('I have installed {string} locally into the path') do |app|
install_binary_and_add_to_path(app, 'bin')
end

def install_binary_and_add_to_path(app, path)
exe = File.join(aruba.root_directory, path, app)
raise "'#{exe}' not found; did you run 'make build'?" unless File.exist?(exe)

expanded_path = expand_path(path)

create_directory(path)
FileUtils.cp(exe, expanded_path)

# TODO: find a way to assert the dest path exists without listing the folder contents to stdoout
# run_command_and_stop("ls -la #{path}", fail_on_error: true)

unless ENV['PATH'].split(File::PATH_SEPARATOR).include?(path)
prepend_environment_variable "PATH", expand_path(path) + File::PATH_SEPARATOR
end
end
20 changes: 20 additions & 0 deletions features/step_definitions/run_steps.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

Given('I clear the screen') do
aruba.command_monitor.clear
end

# Then(/(the )?(\w+) should (not )?show usage/) do |_, channel, negated|
Then(/the (\w+) should show usage/) do |channel|
negated = false
validate_channel(
channel: channel,
negated: negated,
match_as_regex: false,
content: "Usage:"
)
end

When('I sleep for {int} seconds') do |n_seconds|
sleep(n_seconds)
end
72 changes: 72 additions & 0 deletions features/support/command_step_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# frozen_string_literal: true

require 'aruba'
require 'aruba/cucumber'

# Aruba ------------------------------------------------

# aruba file matchers: https://github.com/cucumber/aruba/blob/master/lib/aruba/cucumber/file.rb
# aruba command matchers: https://github.com/cucumber/aruba/blob/master/lib/aruba/cucumber/command.rb

# Step Helpers -----------------------------------------------

module Command
# syntax wrappers around arub commands
module StepHelpers

# this helper provides some common logic around running a specific
# command using Aruba's environment and path resolution, then
# inspecting that output and matching it either as a regex match
# or a string contains
def run_command_and_validate_channel(cmd: '', fail_on_error: true, channel: 'stdout', negated: false, match_as_regex: false, content: '')
run_command_and_stop(cmd, fail_on_error: fail_on_error)

command = aruba.command_monitor.find(Aruba.platform.detect_ruby(cmd))

matcher = case channel
when 'output'; then :have_output
when 'stderr'; then :have_output_on_stderr
when 'stdout'; then :have_output_on_stdout
end

output_string_matcher = if match_as_regex
:an_output_string_matching
else
:an_output_string_including
end

if negated
expect(command).not_to send(matcher, send(output_string_matcher, content))
else
expect(command).to send(matcher, send(output_string_matcher, content))
end
end

def validate_channel(channel: 'stdout', negated: false, match_as_regex: false, content: '')
output = send("all_#{channel}")

matcher = case channel
when 'output'; then :have_output
when 'stderr'; then :have_output_on_stderr
when 'stdout'; then :have_output_on_stdout
end

output_string_matcher = if match_as_regex
:match_output_string
else
:include_output_string
end

if negated
expect(output).not_to send(output_string_matcher, content)
else
expect(output).to send(output_string_matcher, content)
end
end
end
end

# rubocop:disable Style/MixinUsage
# this extend is at the global scope deliberately, to make these helpers available in step definitions
include Command::StepHelpers
# rubocop:enable Style/MixinUsage
32 changes: 32 additions & 0 deletions features/support/hooks.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
require_relative 'os_helper'

Before('@announce-paths') do
aruba.announcer.activate :paths
end

Before('@windows-only') do
pending unless ProjectUtils::OS.windows?
end

Before('@not-windows') do
pending if ProjectUtils::OS.windows?
end

Before('@pending') do
pending
end

Before do
aruba.announcer.announce(:paths, "aruba - root_directory: #{aruba.root_directory}")
aruba.announcer.announce(:paths, "aruba - working_directory: #{File.join(aruba.root_directory, aruba.config.working_directory)}")
aruba.announcer.announce(:paths, "aruba - current_directory: #{File.join(aruba.root_directory, aruba.current_directory)}")

# ensure that git, when run by aruba, does not accidentally discover
# any of the parent repo's git config
ceiling_dir = File.dirname(expand_path('.'))
set_environment_variable('GIT_CEILING_DIRECTORIES', ceiling_dir)
aruba.announcer.announce(:paths, "git ceiling set to: #{ceiling_dir} (git won't look for config or .git/ in this folder or above)")
end

After do
end
15 changes: 15 additions & 0 deletions features/support/matchers/match_string.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
require "rspec/expectations/version"

RSpec::Matchers.define :match_string do |expected|
match do |actual|
actual.force_encoding("UTF-8")
@expected = Regexp.new(unescape_text(expected), Regexp::MULTILINE)
@actual = sanitize_text(actual)

values_match? @expected, @actual
end

diffable

description { "string matches: #{description_of expected}" }
end
30 changes: 30 additions & 0 deletions features/support/os_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module ProjectUtils
# syntax helpers for OS platform detection
# adapted from: https://stackoverflow.com/a/171011/8997
module OS
def OS.windows?
(/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM) != nil
end

def OS.mac?
(/darwin/ =~ RUBY_PLATFORM) != nil
end

def OS.unix?
!OS.windows?
end

def OS.linux?
OS.unix? and not OS.mac?
end

def OS.jruby?
RUBY_ENGINE == 'jruby'
end
end
end

# rubocop:disable Style/MixinUsage
# this extend is at the global scope deliberately, to make these helpers available in step definitions
include ProjectUtils::OS
# rubocop:enable Style/MixinUsage
85 changes: 85 additions & 0 deletions features/support/step_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# frozen_string_literal: true

require 'aruba'
require 'aruba/cucumber'
require_relative 'matchers/match_string'
require 'yaml'

# Aruba ------------------------------------------------

# aruba file matchers: https://github.com/cucumber/aruba/blob/master/lib/aruba/cucumber/file.rb
# aruba command matchers: https://github.com/cucumber/aruba/blob/master/lib/aruba/cucumber/command.rb

Aruba.configure do |config|
config.exit_timeout = 1 # seconds
config.command_search_paths = [] # don't automatically include {project}/bin in the PATH during scenario runs
end

# Step Helpers -----------------------------------------------

Given(/PENDING/) do
pending
end

module ProjectUtils
# syntax wrappers around arub commands
module StepHelpers
# def goconfig_bin
# bin = File.join(aruba.root_directory, 'bin', 'goconfig')
# raise "'#{bin}' not found; did you run 'make build'?" unless File.exist?(bin)
#
# bin
# end

def home_dir
relative_dir('~/')
end

def current_dir
relative_dir('.')
end

def relative_dir(path)
abs_dir(path).delete_prefix(aruba.root_directory)[1..-1]
end

def relative_dir_from_abs(path)
path.delete_prefix(aruba.root_directory)[1..-1]
end

def abs_dir(path)
expanded = path
with_environment do
expanded = expand_path(path)
end
expanded
end

# TODO: this syntax will break with aruba 2.1.0
def all_output_includes(s, negated: false)
if negated
expect(all_commands)
.to_not include_an_object have_output an_output_string_including(s)
else
expect(all_commands)
.to include_an_object have_output an_output_string_including(s)
end
end

# TODO: this syntax will break with aruba 2.1.0
def all_output_matches(s, negated: false)
if negated
expect(all_commands)
.to_not include_an_object have_output an_output_string_matching(s)
else
expect(all_commands)
.to include_an_object have_output an_output_string_matching(s)
end
end
end
end

# rubocop:disable Style/MixinUsage
# this extend is at the global scope deliberately, to make these helpers available in step definitions
include ProjectUtils::StepHelpers
# rubocop:enable Style/MixinUsage

0 comments on commit 37a7698

Please sign in to comment.