diff options
33 files changed, 859 insertions, 0 deletions
diff --git a/qa/.rspec b/qa/.rspec new file mode 100644 index 00000000000..b83d9b7aa65 --- /dev/null +++ b/qa/.rspec @@ -0,0 +1,3 @@ +--color +--format documentation +--require spec_helper diff --git a/qa/Dockerfile b/qa/Dockerfile new file mode 100644 index 00000000000..b4281c02f5a --- /dev/null +++ b/qa/Dockerfile @@ -0,0 +1,15 @@ +FROM ruby:2.3 +LABEL maintainer "Grzegorz Bizon <grzegorz@gitlab.com>" + +RUN sed -i "s/httpredir.debian.org/ftp.us.debian.org/" /etc/apt/sources.list && \ + apt-get update && apt-get install -y --force-yes \ + libqt5webkit5-dev qt5-qmake qt5-default build-essential xvfb git && \ + apt-get clean + + +WORKDIR /home/qa + +COPY ./ ./ +RUN bundle install + +ENTRYPOINT ["bin/test"] diff --git a/qa/Gemfile b/qa/Gemfile new file mode 100644 index 00000000000..baafc976c4b --- /dev/null +++ b/qa/Gemfile @@ -0,0 +1,8 @@ +source 'https://rubygems.org' + +gem 'capybara', '~> 2.12.1' +gem 'capybara-screenshot', '~> 1.0.14' +gem 'capybara-webkit', '~> 1.12.0' +gem 'rake', '~> 12.0.0' +gem 'rspec', '~> 3.5' +gem 'rubocop', '~> 0.47.1' diff --git a/qa/README.md b/qa/README.md new file mode 100644 index 00000000000..2b4577575c5 --- /dev/null +++ b/qa/README.md @@ -0,0 +1,2 @@ +## Integration tests for GitLab + diff --git a/qa/bin/docker b/qa/bin/docker new file mode 100755 index 00000000000..683e915f698 --- /dev/null +++ b/qa/bin/docker @@ -0,0 +1,25 @@ +#!/bin/sh + +case "$1" in + build) + docker pull $CI_REGISTRY_IMAGE:latest + docker build --cache-from $CI_REGISTRY_IMAGE:latest \ + -t $CI_REGISTRY_IMAGE:ce-latest -t $CI_REGISTRY_IMAGE:ee-latest \ + -t $CI_REGISTRY_IMAGE:ce-nightly -t $CI_REGISTRY_IMAGE:ee-nightly \ + -t $CI_REGISTRY_IMAGE:latest . + ;; + publish) + test -n "$CI_BUILD_TOKEN" || exit 1 + docker login --username gitlab-ci-token --password $CI_BUILD_TOKEN registry.gitlab.com + docker push $CI_REGISTRY_IMAGE:latest + docker push $CI_REGISTRY_IMAGE:ce-latest + docker push $CI_REGISTRY_IMAGE:ee-latest + docker push $CI_REGISTRY_IMAGE:ee-nightly + docker push $CI_REGISTRY_IMAGE:ee-nightly + docker logout registry.gitlab.com + ;; + *) + echo "Usage: $0 [build|publish]" + exit 1 + ;; +esac diff --git a/qa/bin/qa b/qa/bin/qa new file mode 100755 index 00000000000..cecdeac14db --- /dev/null +++ b/qa/bin/qa @@ -0,0 +1,7 @@ +#!/usr/bin/env ruby + +require_relative '../qa' + +QA::Scenario + .const_get(ARGV.shift) + .perform(*ARGV) diff --git a/qa/bin/test b/qa/bin/test new file mode 100755 index 00000000000..997392ad6e4 --- /dev/null +++ b/qa/bin/test @@ -0,0 +1,3 @@ +#!/bin/bash + +xvfb-run bundle exec bin/qa $@ diff --git a/qa/qa.rb b/qa/qa.rb new file mode 100644 index 00000000000..c47561bfa18 --- /dev/null +++ b/qa/qa.rb @@ -0,0 +1,83 @@ +$LOAD_PATH << File.expand_path(File.dirname(__FILE__)) + +module QA + ## + # GitLab QA runtime classes, mostly singletons. + # + module Runtime + autoload :User, 'qa/runtime/user' + autoload :Namespace, 'qa/runtime/namespace' + end + + ## + # GitLab QA Scenarios + # + module Scenario + ## + # Support files + # + autoload :Actable, 'qa/scenario/actable' + autoload :Template, 'qa/scenario/template' + + ## + # Test scenario entrypoints. + # + module Test + autoload :Instance, 'qa/scenario/test/instance' + end + + ## + # GitLab instance scenarios. + # + module Gitlab + module Project + autoload :Create, 'qa/scenario/gitlab/project/create' + end + + module License + autoload :Add, 'qa/scenario/gitlab/license/add' + end + end + end + + ## + # Classes describing structure of GitLab, pages, menus etc. + # + # Needed to execute click-driven-only black-box tests. + # + module Page + autoload :Base, 'qa/page/base' + + module Main + autoload :Entry, 'qa/page/main/entry' + autoload :Menu, 'qa/page/main/menu' + autoload :Groups, 'qa/page/main/groups' + autoload :Projects, 'qa/page/main/projects' + end + + module Project + autoload :New, 'qa/page/project/new' + autoload :Show, 'qa/page/project/show' + end + + module Admin + autoload :Menu, 'qa/page/admin/menu' + autoload :License, 'qa/page/admin/license' + end + end + + ## + # Classes describing operations on Git repositories. + # + module Git + autoload :Repository, 'qa/git/repository' + end + + ## + # Classes that make it possible to execute features tests. + # + module Specs + autoload :Config, 'qa/specs/config' + autoload :Runner, 'qa/specs/runner' + end +end diff --git a/qa/qa/git/repository.rb b/qa/qa/git/repository.rb new file mode 100644 index 00000000000..b9e199000d6 --- /dev/null +++ b/qa/qa/git/repository.rb @@ -0,0 +1,71 @@ +require 'uri' + +module QA + module Git + class Repository + include Scenario::Actable + + def self.perform(*args) + Dir.mktmpdir do |dir| + Dir.chdir(dir) { super } + end + end + + def location=(address) + @location = address + @uri = URI(address) + end + + def username=(name) + @username = name + @uri.user = name + end + + def password=(pass) + @password = pass + @uri.password = pass + end + + def use_default_credentials + self.username = Runtime::User.name + self.password = Runtime::User.password + end + + def clone(opts = '') + `git clone #{opts} #{@uri.to_s} ./` + end + + def shallow_clone + clone('--depth 1') + end + + def configure_identity(name, email) + `git config user.name #{name}` + `git config user.email #{email}` + end + + def commit_file(name, contents, message) + add_file(name, contents) + commit(message) + end + + def add_file(name, contents) + File.write(name, contents) + + `git add #{name}` + end + + def commit(message) + `git commit -m "#{message}"` + end + + def push_changes(branch = 'master') + `git push #{@uri.to_s} #{branch}` + end + + def commits + `git log --oneline`.split("\n") + end + end + end +end diff --git a/qa/qa/page/admin/license.rb b/qa/qa/page/admin/license.rb new file mode 100644 index 00000000000..4bdfae30b37 --- /dev/null +++ b/qa/qa/page/admin/license.rb @@ -0,0 +1,20 @@ +module QA + module Page + module Admin + class License < Page::Base + def no_license? + page.has_content?('No GitLab Enterprise Edition ' \ + 'license has been provided yet') + end + + def add_new_license(key) + raise 'License key empty!' if key.to_s.empty? + + choose 'Enter license key' + fill_in 'License key', with: key + click_button 'Upload license' + end + end + end + end +end diff --git a/qa/qa/page/admin/menu.rb b/qa/qa/page/admin/menu.rb new file mode 100644 index 00000000000..b01a4e10f93 --- /dev/null +++ b/qa/qa/page/admin/menu.rb @@ -0,0 +1,19 @@ +module QA + module Page + module Admin + class Menu < Page::Base + def go_to_license + within_middle_menu { click_link 'License' } + end + + private + + def within_middle_menu + page.within('.nav-control') do + yield + end + end + end + end + end +end diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb new file mode 100644 index 00000000000..d55326c5262 --- /dev/null +++ b/qa/qa/page/base.rb @@ -0,0 +1,12 @@ +module QA + module Page + class Base + include Capybara::DSL + include Scenario::Actable + + def refresh + visit current_path + end + end + end +end diff --git a/qa/qa/page/main/entry.rb b/qa/qa/page/main/entry.rb new file mode 100644 index 00000000000..fe80deb6429 --- /dev/null +++ b/qa/qa/page/main/entry.rb @@ -0,0 +1,26 @@ +module QA + module Page + module Main + class Entry < Page::Base + def initialize + visit('/') + + # This resolves cold boot problems with login page + find('.application', wait: 120) + end + + def sign_in_using_credentials + if page.has_content?('Change your password') + fill_in :user_password, with: Runtime::User.password + fill_in :user_password_confirmation, with: Runtime::User.password + click_button 'Change your password' + end + + fill_in :user_login, with: Runtime::User.name + fill_in :user_password, with: Runtime::User.password + click_button 'Sign in' + end + end + end + end +end diff --git a/qa/qa/page/main/groups.rb b/qa/qa/page/main/groups.rb new file mode 100644 index 00000000000..84597719a84 --- /dev/null +++ b/qa/qa/page/main/groups.rb @@ -0,0 +1,20 @@ +module QA + module Page + module Main + class Groups < Page::Base + def prepare_test_namespace + return if page.has_content?(Runtime::Namespace.name) + + click_on 'New Group' + + fill_in 'group_path', with: Runtime::Namespace.name + fill_in 'group_description', + with: "QA test run at #{Runtime::Namespace.time}" + choose 'Private' + + click_button 'Create group' + end + end + end + end +end diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb new file mode 100644 index 00000000000..90ff018b9d2 --- /dev/null +++ b/qa/qa/page/main/menu.rb @@ -0,0 +1,46 @@ +module QA + module Page + module Main + class Menu < Page::Base + def go_to_groups + within_global_menu { click_link 'Groups' } + end + + def go_to_projects + within_global_menu { click_link 'Projects' } + end + + def go_to_admin_area + within_user_menu { click_link 'Admin Area' } + end + + def sign_out + within_user_menu do + find('.header-user-dropdown-toggle').click + click_link('Sign out') + end + end + + def has_personal_area? + page.has_selector?('.header-user-dropdown-toggle') + end + + private + + def within_global_menu + find('.global-dropdown-toggle').click + + page.within('.global-dropdown-menu') do + yield + end + end + + def within_user_menu + page.within('.dropdown-menu-nav') do + yield + end + end + end + end + end +end diff --git a/qa/qa/page/main/projects.rb b/qa/qa/page/main/projects.rb new file mode 100644 index 00000000000..28d3a424022 --- /dev/null +++ b/qa/qa/page/main/projects.rb @@ -0,0 +1,16 @@ +module QA + module Page + module Main + class Projects < Page::Base + def go_to_new_project + ## + # There are 'New Project' and 'New project' buttons on the projects + # page, so we can't use `click_on`. + # + button = find('a', text: /^new project$/i) + button.click + end + end + end + end +end diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb new file mode 100644 index 00000000000..b31bec27b59 --- /dev/null +++ b/qa/qa/page/project/new.rb @@ -0,0 +1,24 @@ +module QA + module Page + module Project + class New < Page::Base + def choose_test_namespace + find('#s2id_project_namespace_id').click + find('.select2-result-label', text: Runtime::Namespace.name).click + end + + def choose_name(name) + fill_in 'project_path', with: name + end + + def add_description(description) + fill_in 'project_description', with: description + end + + def create_new_project + click_on 'Create project' + end + end + end + end +end diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb new file mode 100644 index 00000000000..56a270d8fcc --- /dev/null +++ b/qa/qa/page/project/show.rb @@ -0,0 +1,23 @@ +module QA + module Page + module Project + class Show < Page::Base + def choose_repository_clone_http + find('#clone-dropdown').click + + page.within('#clone-dropdown') do + find('span', text: 'HTTP').click + end + end + + def repository_location + find('#project_clone').value + end + + def wait_for_push + sleep 5 + end + end + end + end +end diff --git a/qa/qa/runtime/namespace.rb b/qa/qa/runtime/namespace.rb new file mode 100644 index 00000000000..e4910b63a14 --- /dev/null +++ b/qa/qa/runtime/namespace.rb @@ -0,0 +1,15 @@ +module QA + module Runtime + module Namespace + extend self + + def time + @time ||= Time.now + end + + def name + 'qa_test_' + time.strftime('%d_%m_%Y_%H-%M-%S') + end + end + end +end diff --git a/qa/qa/runtime/user.rb b/qa/qa/runtime/user.rb new file mode 100644 index 00000000000..12ceda015f0 --- /dev/null +++ b/qa/qa/runtime/user.rb @@ -0,0 +1,15 @@ +module QA + module Runtime + module User + extend self + + def name + ENV['GITLAB_USERNAME'] || 'root' + end + + def password + ENV['GITLAB_PASSWORD'] || 'test1234' + end + end + end +end diff --git a/qa/qa/scenario/actable.rb b/qa/qa/scenario/actable.rb new file mode 100644 index 00000000000..6cdbd24780e --- /dev/null +++ b/qa/qa/scenario/actable.rb @@ -0,0 +1,23 @@ +module QA + module Scenario + module Actable + def act(*args, &block) + instance_exec(*args, &block) + end + + def self.included(base) + base.extend(ClassMethods) + end + + module ClassMethods + def perform + yield new if block_given? + end + + def act(*args, &block) + new.act(*args, &block) + end + end + end + end +end diff --git a/qa/qa/scenario/gitlab/license/add.rb b/qa/qa/scenario/gitlab/license/add.rb new file mode 100644 index 00000000000..ca5e1176959 --- /dev/null +++ b/qa/qa/scenario/gitlab/license/add.rb @@ -0,0 +1,21 @@ +module QA + module Scenario + module Gitlab + module License + class Add < Scenario::Template + def perform + Page::Main::Entry.act { sign_in_using_credentials } + Page::Main::Menu.act { go_to_admin_area } + Page::Admin::Menu.act { go_to_license } + + Page::Admin::License.act do + add_new_license(ENV['EE_LICENSE']) if no_license? + end + + Page::Main::Menu.act { sign_out } + end + end + end + end + end +end diff --git a/qa/qa/scenario/gitlab/project/create.rb b/qa/qa/scenario/gitlab/project/create.rb new file mode 100644 index 00000000000..38522714e64 --- /dev/null +++ b/qa/qa/scenario/gitlab/project/create.rb @@ -0,0 +1,31 @@ +require 'securerandom' + +module QA + module Scenario + module Gitlab + module Project + class Create < Scenario::Template + attr_writer :description + + def name=(name) + @name = "#{name}-#{SecureRandom.hex(8)}" + end + + def perform + Page::Main::Menu.act { go_to_groups } + Page::Main::Groups.act { prepare_test_namespace } + Page::Main::Menu.act { go_to_projects } + Page::Main::Projects.act { go_to_new_project } + + Page::Project::New.perform do |page| + page.choose_test_namespace + page.choose_name(@name) + page.add_description(@description) + page.create_new_project + end + end + end + end + end + end +end diff --git a/qa/qa/scenario/template.rb b/qa/qa/scenario/template.rb new file mode 100644 index 00000000000..341998af160 --- /dev/null +++ b/qa/qa/scenario/template.rb @@ -0,0 +1,16 @@ +module QA + module Scenario + class Template + def self.perform(*args) + new.tap do |scenario| + yield scenario if block_given? + return scenario.perform(*args) + end + end + + def perform(*_args) + raise NotImplementedError + end + end + end +end diff --git a/qa/qa/scenario/test/instance.rb b/qa/qa/scenario/test/instance.rb new file mode 100644 index 00000000000..dcd0a32d79d --- /dev/null +++ b/qa/qa/scenario/test/instance.rb @@ -0,0 +1,27 @@ +module QA + module Scenario + module Test + ## + # Run test suite against any GitLab instance, + # including staging and on-premises installation. + # + class Instance < Scenario::Template + def perform(address, tag, *files) + Specs::Config.perform do |specs| + specs.address = address + end + + ## + # Temporary CE + EE support + Scenario::Gitlab::License::Add.perform if tag.to_s == 'ee' + + Specs::Runner.perform do |specs| + files = files.any? ? files : 'qa/specs/features' + + specs.rspec('--tty', '--tag', tag.to_s, files) + end + end + end + end + end +end diff --git a/qa/qa/specs/config.rb b/qa/qa/specs/config.rb new file mode 100644 index 00000000000..d72187fcd34 --- /dev/null +++ b/qa/qa/specs/config.rb @@ -0,0 +1,78 @@ +require 'rspec/core' +require 'capybara/rspec' +require 'capybara-webkit' +require 'capybara-screenshot/rspec' + +# rubocop:disable Metrics/MethodLength +# rubocop:disable Metrics/LineLength + +module QA + module Specs + class Config < Scenario::Template + attr_writer :address + + def initialize + @address = ENV['GITLAB_URL'] + end + + def perform + raise 'Please configure GitLab address!' unless @address + + configure_rspec! + configure_capybara! + configure_webkit! + end + + def configure_rspec! + RSpec.configure do |config| + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`. + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # Run specs in random order to surface order dependencies. + config.order = :random + Kernel.srand config.seed + + config.before(:all) do + page.current_window.resize_to(1200, 1800) + end + + config.formatter = :documentation + config.color = true + end + end + + def configure_capybara! + Capybara.configure do |config| + config.app_host = @address + config.default_driver = :webkit + config.javascript_driver = :webkit + config.default_max_wait_time = 4 + + # https://github.com/mattheworiordan/capybara-screenshot/issues/164 + config.save_path = 'tmp' + end + end + + def configure_webkit! + Capybara::Webkit.configure do |config| + config.allow_url(@address) + config.block_unknown_urls + end + rescue RuntimeError # rubocop:disable Lint/HandleExceptions + # TODO, Webkit is already configured, this make this + # configuration step idempotent, should be improved. + end + end + end +end diff --git a/qa/qa/specs/features/login/standard_spec.rb b/qa/qa/specs/features/login/standard_spec.rb new file mode 100644 index 00000000000..ecb3f0cb68c --- /dev/null +++ b/qa/qa/specs/features/login/standard_spec.rb @@ -0,0 +1,14 @@ +module QA + feature 'standard root login', :ce, :ee do + scenario 'user logs in using credentials' do + Page::Main::Entry.act { sign_in_using_credentials } + + # TODO, since `Signed in successfully` message was removed + # this is the only way to tell if user is signed in correctly. + # + Page::Main::Menu.perform do |menu| + expect(menu).to have_personal_area + end + end + end +end diff --git a/qa/qa/specs/features/project/create_spec.rb b/qa/qa/specs/features/project/create_spec.rb new file mode 100644 index 00000000000..cf4226252a6 --- /dev/null +++ b/qa/qa/specs/features/project/create_spec.rb @@ -0,0 +1,19 @@ +module QA + feature 'create a new project', :ce, :ee, :staging do + scenario 'user creates a new project' do + Page::Main::Entry.act { sign_in_using_credentials } + + Scenario::Gitlab::Project::Create.perform do |project| + project.name = 'awesome-project' + project.description = 'create awesome project test' + end + + expect(page).to have_content( + /Project \S?awesome-project\S+ was successfully created/ + ) + + expect(page).to have_content('create awesome project test') + expect(page).to have_content('The repository for this project is empty') + end + end +end diff --git a/qa/qa/specs/features/repository/clone_spec.rb b/qa/qa/specs/features/repository/clone_spec.rb new file mode 100644 index 00000000000..a772dc227e3 --- /dev/null +++ b/qa/qa/specs/features/repository/clone_spec.rb @@ -0,0 +1,57 @@ +module QA + feature 'clone code from the repository', :ce, :ee, :staging do + context 'with regular account over http' do + given(:location) do + Page::Project::Show.act do + choose_repository_clone_http + repository_location + end + end + + before do + Page::Main::Entry.act { sign_in_using_credentials } + + Scenario::Gitlab::Project::Create.perform do |scenario| + scenario.name = 'project-with-code' + scenario.description = 'project for git clone tests' + end + + Git::Repository.perform do |repository| + repository.location = location + repository.use_default_credentials + + repository.act do + clone + configure_identity('GitLab QA', 'root@gitlab.com') + commit_file('test.rb', 'class Test; end', 'Add Test class') + commit_file('README.md', '# Test', 'Add Readme') + push_changes + end + end + end + + scenario 'user performs a deep clone' do + Git::Repository.perform do |repository| + repository.location = location + repository.use_default_credentials + + repository.act { clone } + + expect(repository.commits.size).to eq 2 + end + end + + scenario 'user performs a shallow clone' do + Git::Repository.perform do |repository| + repository.location = location + repository.use_default_credentials + + repository.act { shallow_clone } + + expect(repository.commits.size).to eq 1 + expect(repository.commits.first).to include 'Add Readme' + end + end + end + end +end diff --git a/qa/qa/specs/features/repository/push_spec.rb b/qa/qa/specs/features/repository/push_spec.rb new file mode 100644 index 00000000000..4b6cb7908bb --- /dev/null +++ b/qa/qa/specs/features/repository/push_spec.rb @@ -0,0 +1,39 @@ +module QA + feature 'push code to repository', :ce, :ee, :staging do + context 'with regular account over http' do + scenario 'user pushes code to the repository' do + Page::Main::Entry.act { sign_in_using_credentials } + + Scenario::Gitlab::Project::Create.perform do |scenario| + scenario.name = 'project_with_code' + scenario.description = 'project with repository' + end + + Git::Repository.perform do |repository| + repository.location = Page::Project::Show.act do + choose_repository_clone_http + repository_location + end + + repository.use_default_credentials + + repository.act do + clone + configure_identity('GitLab QA', 'root@gitlab.com') + add_file('README.md', '# This is test project') + commit('Add README.md') + push_changes + end + end + + Page::Project::Show.act do + wait_for_push + refresh + end + + expect(page).to have_content('README.md') + expect(page).to have_content('This is test project') + end + end + end +end diff --git a/qa/qa/specs/runner.rb b/qa/qa/specs/runner.rb new file mode 100644 index 00000000000..83ae15d0995 --- /dev/null +++ b/qa/qa/specs/runner.rb @@ -0,0 +1,15 @@ +require 'rspec/core' + +module QA + module Specs + class Runner + include Scenario::Actable + + def rspec(*args) + RSpec::Core::Runner.run(args.flatten, $stderr, $stdout).tap do |status| + abort if status.nonzero? + end + end + end + end +end diff --git a/qa/spec/scenario/actable_spec.rb b/qa/spec/scenario/actable_spec.rb new file mode 100644 index 00000000000..422763910e4 --- /dev/null +++ b/qa/spec/scenario/actable_spec.rb @@ -0,0 +1,47 @@ +describe QA::Scenario::Actable do + subject do + Class.new do + include QA::Scenario::Actable + + attr_accessor :something + + def do_something(arg = nil) + "some#{arg}" + end + end + end + + describe '.act' do + it 'provides means to run steps' do + result = subject.act { do_something } + + expect(result).to eq 'some' + end + + it 'supports passing variables' do + result = subject.act('thing') do |variable| + do_something(variable) + end + + expect(result).to eq 'something' + end + + it 'returns value from the last method' do + result = subject.act { 'test' } + + expect(result).to eq 'test' + end + end + + describe '.perform' do + it 'makes it possible to pass binding' do + variable = 'something' + + result = subject.perform do |object| + object.something = variable + end + + expect(result).to eq 'something' + end + end +end diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb new file mode 100644 index 00000000000..c07a3234673 --- /dev/null +++ b/qa/spec/spec_helper.rb @@ -0,0 +1,19 @@ +require_relative '../qa' + +RSpec.configure do |config| + config.expect_with :rspec do |expectations| + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + config.mock_with :rspec do |mocks| + mocks.verify_partial_doubles = true + end + + config.shared_context_metadata_behavior = :apply_to_host_groups + config.disable_monkey_patching! + config.expose_dsl_globally = true + config.warnings = true + config.profile_examples = 10 + config.order = :random + Kernel.srand config.seed +end |