diff --git a/.gitignore b/.gitignore index f8f1b33..4cfc2a9 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,7 @@ .project # Doc -doc/build +doc/_build # Editors *.swp diff --git a/Gemfile b/Gemfile index 5db8c61..e010dad 100644 --- a/Gemfile +++ b/Gemfile @@ -5,14 +5,13 @@ group :development, :test do gem 'puppet-lint', '~> 0.3.2' gem 'rspec-puppet', '~> 2.2.0' gem 'rspec-puppet-utils', '~> 2.0.0' - gem 'openstack' - gem 'netaddr' gem 'deep_merge' gem 'pry' gem 'simplecov' gem 'puppet-spec' gem 'colorize' gem 'parallel' + gem 'openstack' end if ENV['PUPPET_GEM_VERSION'] diff --git a/doc/usage.rst b/doc/usage.rst index 59ef789..1294b77 100644 --- a/doc/usage.rst +++ b/doc/usage.rst @@ -89,7 +89,20 @@ their own.:: tests/noop/noop_tests.sh -y neut_vlan.compute.ssl,neut_vlan.compute.nossl -s firewall/firewall,netconfig/netconfig -p -Filters can use used with a list of elements like this. +Filters can use used with a list of elements or can be given as a regular +expression or a list of regular expressions:: + + ./noop_tests.sh -p -s 'master/.*' + +Will filter all tasks in the *master* group.:: + + ./noop_tests.sh -p -s '^ceph.*,^heat.*,glance/db_spec' + +Will filter all *ceph*, *heat* tasks and glance/db_spec task individually.:: + + ./noop_tests.sh -p -s '^ceph.*' -y ceph + +All *ceph* related tasks only on Hiera files which have Ceph enabled. Recreating globals yaml files ----------------------------- @@ -223,6 +236,49 @@ and properties actually have after all the catalog logic is processed. It's very helpful when you are debugging a strange task behaviour or writing a spec file. +The framework can also gather and report information about *File* resources +that are being installed by Puppet. Using *--save_file_resources* options +will dave the list of files that would be installed by the catalog and +description about their source or template. Using *--puppet_binary_files* +option will enable additional RSpec matcher that will fail if there are +files and, especially, binary files being installed. These ones should be +delivered by fuel packages. + +Data-driven catalog tests +------------------------- + +Usually the spec files try to repeat the logic found in the tested manifests, +receive the same set of resources and their parameters and compare them to +the set of resources found in the compiled catalog. Then the matchers are used +to check if the catalog contains what is expected from it to contain. + +While this method works well in most cases it requires a lot of work and +extensive expertise in the tasks' domain to write a correct and comprehensive +set of spec for a task catalog. Specs also cannot detect if there are several +new resources or properties that have not been described in the spec file. + +Data-driven tests can offer an alternative way to ensure that there are +no unwanted changes in the tasks catalogs. The idea behind them is building +catalogs in human-readable format before and after the changes are made. Then +these files can be compared and everything that have been changes will become +visible. + +Using the **-V** options will save the current catalog to the *catalogs* +folder. These generated catalogs can either be commited to the repository +or be used as a temporary files that you can make before doing some changes +to the modules or manifests and removed later. Saved catalog files can be +very useful for a developer to just review the catalog contents and check +that every resource or class are receiving the correct property values. + +You can also use **-v** option to enable automatic catalog checks. It should be +done after you have generated the initial versions and made some changes. +Running the tests with this option enabled will generate the catalogs again and +compare them to the saved version. If there are differences the test will be +failed and you will be able to locate the failed tasks. If you have catalogs +commited to the repository you can use the *diff* command to review what +changes to the catalog files have been introduced and commit the modified +catalogs if the changes are expected. + Using external environment variables and custom paths ----------------------------------------------------- @@ -241,7 +297,7 @@ Paths related: library. - **SPEC_TASK_DIR** Set the path to the task manifests folder. - **SPEC_DEPLOYMENT_DIR** Set the path to the *deployment* directory. It's - actually use only to find the scripts to udate and reset modules. + actually use only to find the scripts to update and reset modules. - **WORKSPACE** This variable is passed by the Jenkins jobs or will default to the *workspece* folder. Currently used only to store the Ruby gems installed by the *bundler* if *RVM* is not used. diff --git a/doc/utility.rst b/doc/utility.rst index 460ed03..c5e4b34 100644 --- a/doc/utility.rst +++ b/doc/utility.rst @@ -27,7 +27,6 @@ Output::: -O, --report_only_tasks Show only tasks, skip individual examples -r, --load_saved_reports Read saved report JSON files from the previous run and show tasks report -R, --run_failed_tasks Run the task that have previously failed again - -M, --list_missing List all task manifests without a spec file -x, --xunit_report Save report in xUnit format to a file List options: -Y, --list_hiera List all hiera yaml files @@ -40,7 +39,7 @@ Output::: -f, --facts FACTS1,FACTS2 Run only these facts yamls. Example: "ubuntu.yaml,centos.yaml" Debug options: -c, --task_console Run PRY console - -C, --rspec_console Run PRY console in the + -C, --rspec_console Run PRY console in the RSpec process -d, --task_debug Show framework debug messages -D, --puppet_debug Show Puppet debug messages --debug_log FILE Write all debug messages to this files @@ -56,7 +55,11 @@ Output::: --dir_puppet_modules DIR Path to the puppet modules Spec options: -A, --catalog_show Show catalog content debug output + -V, --catalog_save Save catalog to the files instead of comparing them with the current catalogs + -v, --catalog_check Check the saved catalog against the current one -a, --spec_status Show spec status blocks + --puppet_binary_files Check if Puppet installs binary files + --save_file_resources Save file resources list to a report file Shortcut scripts ---------------- @@ -78,5 +81,6 @@ can be used to perform some common actions. the numbers of tasks in the library. - **run_failed_tasks.sh** This wrapper will load the saved reports files from the previous run and will try to run all the failed tasks again. -- **purge_reports.sh** Removes all report files. -- **purge_globals.sh** Removes all globals files. +- **purge_reports.sh** Removes all task report files. +- **purge_globals.sh** Removes all saved globals files. +- **purge_catalogs.sh** Removes all saves catalog files. diff --git a/facts/master_centos6.yaml b/facts/master_centos6.yaml index 3a2135d..d565cd9 100644 --- a/facts/master_centos6.yaml +++ b/facts/master_centos6.yaml @@ -19,3 +19,8 @@ :l23_os: 'centos6' :os_package_type: 'rpm' :os_service_default: '' +:interfaces: docker0,eth0,lo +:ipaddress: 172.17.42.1 +:ipaddress_docker0: 172.17.42.1 +:ipaddress_eth0: 10.20.0.2 +:ipaddress_lo: 127.0.0.1 diff --git a/facts/master_centos7.yaml b/facts/master_centos7.yaml index 7c5b896..41e7ca7 100644 --- a/facts/master_centos7.yaml +++ b/facts/master_centos7.yaml @@ -19,3 +19,8 @@ :l23_os: 'centos6' :os_package_type: 'rpm' :os_service_default: '' +:interfaces: docker0,eth0,lo +:ipaddress: 172.17.42.1 +:ipaddress_docker0: 172.17.42.1 +:ipaddress_eth0: 10.20.0.2 +:ipaddress_lo: 127.0.0.1 diff --git a/hiera/globals/neut_vlan.cinder-block-device.compute.yaml b/hiera/globals/neut_vlan.cinder-block-device.compute.yaml index 2a6a93d..f69bafd 100644 --- a/hiera/globals/neut_vlan.cinder-block-device.compute.yaml +++ b/hiera/globals/neut_vlan.cinder-block-device.compute.yaml @@ -389,10 +389,31 @@ - controller sql_connection: "mysql://nova:wPFuGzlctG0bWM5i94Xsu1ub@192.168.0.2/nova?charset=utf8&read_timeout=60" storage_hash: + iser: false + volumes_ceph: false + per_pool_pg_nums: + compute: 128 + default_pg_num: 128 + volumes: 128 + images: 128 + backups: 128 + ".rgw": 128 + objects_ceph: false + ephemeral_ceph: false + volumes_lvm: true + volumes_block_device: true + images_vcenter: false + osd_pool_size: "2" + pg_num: 128 + images_ceph: false + metadata: + group: storage + weight: 60 + label: Storage volume_backend_names: volumes_ceph: false - volumes_lvm: false - volumes_block_device: false + volumes_lvm: LVM-backend + volumes_block_device: BDD-backend swift_hash: user_password: zGlRsQYt0T5Cg8Mmw5bEhtvg syslog_hash: diff --git a/hiera/master.yaml b/hiera/master.yaml index e6c163e..cd469bf 100644 --- a/hiera/master.yaml +++ b/hiera/master.yaml @@ -1,76 +1,97 @@ -# fqdn is needed for tests internals -fqdn: "fuel.domain.tld" -role: "master" - -# run only these tasks with the master node manifest -test_tasks: -- master/astute-only -- master/cobbler-only -- master/dhcp-default-range -- master/hiera-for-container -- master/host-only -- master/host-upgrade -- master/keystone-only -- master/nailgun-only -- master/nginx-only -- master/ostf-only -- master/postgres-only -- master/puppetsync-only -- master/rabbitmq-only -- master/rsyslog-only - -# Full astute.yaml for new-style manifests that load it via Hiera -"HOSTNAME": "nailgun" +--- +"ADMIN_NETWORK": + "cidr": "10.20.0.0/24" + "dhcp_gateway": "10.109.0.1" + "dhcp_pool_end": "10.109.0.254" + "dhcp_pool_start": "10.109.0.3" + "interface": "enp0s3" + "ipaddress": "10.109.0.2" + "mac": "64:85:8e:4f:7c:2e" + "netmask": "255.255.255.0" + "size": "256" +"BOOTSTRAP": + "flavor": "ubuntu" + "http_proxy": "" + "https_proxy": "" + "repos": + - "name": "ubuntu" + "priority": !!null "null" + "section": "main universe multiverse" + "suite": "trusty" + "type": "deb" + "uri": "http://archive.ubuntu.com/ubuntu" + - "name": "ubuntu-updates" + "priority": !!null "null" + "section": "main universe multiverse" + "suite": "trusty-updates" + "type": "deb" + "uri": "http://archive.ubuntu.com/ubuntu" + - "name": "ubuntu-security" + "priority": !!null "null" + "section": "main universe multiverse" + "suite": "trusty-security" + "type": "deb" + "uri": "http://archive.ubuntu.com/ubuntu" + - "name": "mos" + "priority": !!int "1050" + "section": "main restricted" + "suite": "mos9.0" + "type": "deb" + "uri": "http://127.0.0.1:8080/ubuntu/x86_64" + - "name": "mos-updates" + "priority": !!int "1050" + "section": "main restricted" + "suite": "mos9.0-updates" + "type": "deb" + "uri": "http://mirror.fuel-infra.org/mos-repos/ubuntu/9.0" + - "name": "mos-security" + "priority": !!int "1050" + "section": "main restricted" + "suite": "mos9.0-security" + "type": "deb" + "uri": "http://mirror.fuel-infra.org/mos-repos/ubuntu/9.0" + - "name": "mos-holdback" + "priority": !!int "1100" + "section": "main restricted" + "suite": "mos9.0-holdback" + "type": "deb" + "uri": "http://mirror.fuel-infra.org/mos-repos/ubuntu/9.0" "DNS_DOMAIN": "test.domain.local" "DNS_SEARCH": "test.domain.local" "DNS_UPSTREAM": "10.109.0.1" +"FEATURE_GROUPS": [] +"FUEL_ACCESS": + "password": "admin" + "user": "admin" +"HOSTNAME": "nailgun" "NTP1": "0.fuel.pool.ntp.org" "NTP2": "1.fuel.pool.ntp.org" "NTP3": "2.fuel.pool.ntp.org" -"ADMIN_NETWORK": - "interface": "eth0" - "ipaddress": "10.109.0.2" - "netmask": "255.255.255.0" - "cidr": "10.20.0.0/24" - "size": "256" - "dhcp_gateway": "10.109.0.1" - "dhcp_pool_start": "10.109.0.3" - "dhcp_pool_end": "10.109.0.254" - "mac": "64:60:46:2e:5d:37" -"FUEL_ACCESS": - "user": "admin" - "password": "admin" -"BOOTSTRAP": - "MIRROR_DISTRO": "http://archive.ubuntu.com/ubuntu" - "MIRROR_MOS": "http://mirror.fuel-infra.org/mos-repos/ubuntu/9.0" - "HTTP_PROXY": "" - "EXTRA_APT_REPOS": "" - "flavor": "centos" "PRODUCTION": "docker" +"astute": + "password": "4OVylZOm" + "user": "naily" +"cobbler": + "password": "JuYBYPGy" + "user": "cobbler" +"keystone": + "admin_token": "ED6pa5u5" + "monitord_password": "rJ1GbiXg" + "monitord_user": "monitord" + "nailgun_password": "koftnFua" + "nailgun_user": "nailgun" + "ostf_password": "S1V7UGP3" + "ostf_user": "ostf" +"mcollective": + "password": "r3JATyRN" + "user": "mcollective" "postgres": "keystone_dbname": "keystone" - "nailgun_user": "nailgun" + "keystone_password": "ab32l0zA" "keystone_user": "keystone" - "nailgun_password": "CYoU6RS6" - "ostf_user": "ostf" "nailgun_dbname": "nailgun" - "keystone_password": "cpppakUb" - "ostf_dbname": "ostf" - "ostf_password": "TwfzylM7" -"cobbler": - "password": "0mMXE4t8" - "user": "cobbler" -"astute": - "password": "SwLCUx2H" - "user": "naily" -"keystone": + "nailgun_password": "Zk9zJ6Cy" "nailgun_user": "nailgun" - "monitord_user": "monitord" - "nailgun_password": "MtC5S2TN" - "monitord_password": "9IR0gsgd" + "ostf_dbname": "ostf" + "ostf_password": "pErpTajp" "ostf_user": "ostf" - "admin_token": "ZoyxrMO6" - "ostf_password": "7evzsSBv" -"mcollective": - "password": "PPMi1XT2" - "user": "mcollective" diff --git a/hiera/neut_vlan.cinder-block-device.compute.yaml b/hiera/neut_vlan.cinder-block-device.compute.yaml index 0c431e2..f027c41 100644 --- a/hiera/neut_vlan.cinder-block-device.compute.yaml +++ b/hiera/neut_vlan.cinder-block-device.compute.yaml @@ -121,7 +121,7 @@ access: user: admin tenant: admin email: admin@localhost -storage_hash: +storage: iser: false volumes_ceph: false per_pool_pg_nums: diff --git a/lib/noop/manager.rb b/lib/noop/manager.rb index 1ed6e47..89886ca 100644 --- a/lib/noop/manager.rb +++ b/lib/noop/manager.rb @@ -1,7 +1,7 @@ require_relative 'config' require_relative 'manager/library' require_relative 'manager/options' -require_relative 'manager/actions' +require_relative 'manager/base' require_relative 'manager/report' require_relative 'manager/setup' require_relative 'manager/xunit' diff --git a/lib/noop/manager/actions.rb b/lib/noop/manager/actions.rb deleted file mode 100644 index 139aba7..0000000 --- a/lib/noop/manager/actions.rb +++ /dev/null @@ -1,153 +0,0 @@ -require 'parallel' - -module Noop - class Manager - def find_tasks_without_specs - task_file_names.reject do |manifest| - spec = Noop::Utils.convert_to_spec manifest - spec_file_names.include? spec - end - end - - def debug(message) - Noop::Config.log.debug message - end - - def output(message) - puts message - end - - def parallel_run? - options[:parallel_run] and options[:parallel_run] > 0 - end - - def list_hiera_files - hiera_file_names.sort.each do |file_name_hiera| - next unless hiera_included? file_name_hiera - output file_name_hiera - end - exit(0) - end - - def list_facts_files - facts_file_names.sort.each do |file_name_facts| - next unless facts_included? file_name_facts - output file_name_facts - end - exit(0) - end - - def list_spec_files - spec_file_names.sort.each do |file_name_spec| - next unless spec_included? file_name_spec - output file_name_spec - end - exit(0) - end - - def list_task_files - task_file_names.sort.each do |file_name_task| - output file_name_task - end - exit(0) - end - - def run_all_tasks - Parallel.map(task_list, :in_threads => options[:parallel_run]) do |task| - task.run unless options[:pretend] - task - end - end - - def run_failed_tasks - Parallel.map(task_list, :in_threads => options[:parallel_run]) do |task| - next if task.success? - task.status = :pending - task.run unless options[:pretend] - task - end - end - - def load_task_reports - Parallel.map(task_list, :in_threads => options[:parallel_run]) do |task| - task.file_load_report_json - task.determine_task_status - task - end - end - - def list_tasks_without_specs - tasks_without_specs = find_tasks_without_specs.to_a - if tasks_without_specs.any? - Noop::Utils.error "There are tasks without specs: #{tasks_without_specs.join ', '}" - end - end - - def have_failed_tasks? - task_list.any? do |task| - task.failed? - end - end - - def exit_with_error_code - exit 1 if have_failed_tasks? - exit 0 - end - -######################################### - - def main - options - - if ENV['SPEC_TASK_CONSOLE'] - require 'pry' - binding.pry - exit(0) - end - - if options[:list_missing] - list_tasks_without_specs - end - - if options[:bundle_setup] - setup_bundle - end - - if options[:update_librarian_puppet] - setup_library - end - - if options[:self_check] - check_paths - show_filters - show_library - exit(0) - end - - list_hiera_files if options[:list_hiera] - list_facts_files if options[:list_facts] - list_spec_files if options[:list_specs] - list_task_files if options[:list_tasks] - - if options[:run_failed_tasks] - load_task_reports - run_failed_tasks - task_report - exit_with_error_code - end - - if options[:load_saved_reports] - load_task_reports - task_report - save_xunit_report if options[:xunit_report] - exit_with_error_code - end - - run_all_tasks - task_report - save_xunit_report if options[:xunit_report] - exit_with_error_code - end - - end -end diff --git a/lib/noop/manager/base.rb b/lib/noop/manager/base.rb new file mode 100644 index 0000000..53ea6b1 --- /dev/null +++ b/lib/noop/manager/base.rb @@ -0,0 +1,209 @@ +module Noop + class Manager + def initialize + options + colorize_load_or_stub + parallel_load_or_stub + end + + # Load the "colorize" gem or just + # stub the colorization method if the gem + # cannot be loaded and don't show colors + def colorize_load_or_stub + begin + require 'colorize' + rescue LoadError + debug 'Could not load "colorize" gem. Disabling colors.' + String.instance_eval do + define_method(:colorize) do |*args| + self + end + end + end + end + + # Load the 'parallel' gem or just + # stub the parallel run function to run tasks one by one + def parallel_load_or_stub + begin + require 'parallel' + rescue LoadError + debug 'Could not load "parallel" gem. Disabling multi-process run.' + Object.const_set('Parallel', Module.new) + class << Parallel + def map(data, *args, &block) + data.map &block + end + end + end + end + + # Write a debug message to the logger + # @return [void] + def debug(message) + Noop::Config.log.debug message + end + + # Output a message to the console + # @return [void] + def output(message) + Noop::Utils.output message + end + + # Output an error message to the log file + # and raise the exception + # @return [void] + def error(message) + Noop::Utils.error message + end + + # Check if parallel run option is enabled + # @return [true,false] + def parallel_run? + options[:parallel_run] and options[:parallel_run] > 0 + end + + # Check if there are some filters defined + # @return [true,false] + def has_filters? + options[:filter_specs] or options[:filter_facts] or options[:filter_hiera] or options[:filter_examples] + end + + # Output a list of all discovered Hiera file names taking filers into account + # @return [void] + def list_hiera_files + hiera_file_names.sort.each do |file_name_hiera| + next unless hiera_included? file_name_hiera + output file_name_hiera + end + exit(0) + end + + # Output a list of all discovered facts file names taking filers into account + # @return [void] + def list_facts_files + facts_file_names.sort.each do |file_name_facts| + next unless facts_included? file_name_facts + output file_name_facts + end + exit(0) + end + + # Output a list of all discovered spec file names taking filers into account + # @return [void] + def list_spec_files + spec_file_names.sort.each do |file_name_spec| + next unless spec_included? file_name_spec + output file_name_spec + end + exit(0) + end + + # Output a list of all discovered task file names taking filers into account + # @return [void] + def list_task_files + task_file_names.sort.each do |file_name_task| + output file_name_task + end + exit(0) + end + + # Try to run all discovered tasks in the task list, using + # parallel run if enabled + # Does not run tasks if :pretend option is given + # return [Array] + def run_all_tasks + Parallel.map(task_list, :in_threads => options[:parallel_run]) do |task| + task.run unless options[:pretend] + task + end + end + + # Try to run anly those tasks that have failed status by reseting them + # to the :pending status first. + # Does not run tasks if :pretend option is given + # return [Array] + def run_failed_tasks + Parallel.map(task_list, :in_threads => options[:parallel_run]) do |task| + next if task.success? + task.status = :pending + task.run unless options[:pretend] + task + end + end + + # Ask every task in the task list to load its report file and status + # from the previous run attempt + # return [Array] + def load_task_reports + Parallel.map(task_list, :in_threads => options[:parallel_run]) do |task| + task.file_load_report_json + task.determine_task_status + task + end + end + + # Check if there are any failed tasks in the list. + # @return [true, false] + def have_failed_tasks? + task_list.any? do |task| + task.failed? + end + end + + # Exit with error if there are failed tasks + # or without the error code if none. + def exit_with_error_code + exit 1 if have_failed_tasks? + exit 0 + end + +######################################### + + def main + if ENV['SPEC_TASK_CONSOLE'] + require 'pry' + binding.pry + exit(0) + end + + if options[:bundle_setup] + setup_bundle + end + + if options[:update_librarian_puppet] + setup_library + end + + if options[:self_check] + self_check + exit(0) + end + + list_hiera_files if options[:list_hiera] + list_facts_files if options[:list_facts] + list_spec_files if options[:list_specs] + list_task_files if options[:list_tasks] + + if options[:run_failed_tasks] + load_task_reports + run_failed_tasks + tasks_report + exit_with_error_code + end + + if options[:load_saved_reports] + load_task_reports + tasks_report + save_xunit_report if options[:xunit_report] and not options[:pretend] + exit_with_error_code + end + + run_all_tasks + tasks_report + save_xunit_report if options[:xunit_report] and not options[:pretend] + exit_with_error_code + end + + end +end diff --git a/lib/noop/manager/library.rb b/lib/noop/manager/library.rb index 6b2d872..889edb3 100644 --- a/lib/noop/manager/library.rb +++ b/lib/noop/manager/library.rb @@ -4,67 +4,80 @@ require 'set' module Noop class Manager + # Recursively find file in the folder + # @param root [String,Pathname] + # @return [Array] + def find_files(root, path_from=nil, &block) + root = Noop::Utils.convert_to_path root + files = [] + begin + root.children.each do |path| + if path.file? + if block_given? + next unless block.call path + end + path = path.relative_path_from path_from if path_from + files << path + else + files << find_files(path, path_from, &block) + end + end + rescue + [] + end + files.flatten + end + + # Scan the spec directory and gather the list of spec files # @return [Array] def spec_file_names return @spec_file_names if @spec_file_names - @spec_file_names = [] - Noop::Utils.error "No #{Noop::Config.dir_path_task_spec} directory!" unless Noop::Config.dir_path_task_spec.directory? - Noop::Config.dir_path_task_spec.find do |spec_file| - next unless spec_file.file? - next unless spec_file.to_s.end_with? '_spec.rb' - @spec_file_names << spec_file.relative_path_from(Noop::Config.dir_path_task_spec) + error "No #{Noop::Config.dir_path_task_spec} directory!" unless Noop::Config.dir_path_task_spec.directory? + @spec_file_names = find_files(Noop::Config.dir_path_task_spec, Noop::Config.dir_path_task_spec) do |file| + file.to_s.end_with? '_spec.rb' end - @spec_file_names end + # Scan the Hiera directory and gather the list of Hiera files # @return [Array] def hiera_file_names return @hiera_file_names if @hiera_file_names - @hiera_file_names = [] - Noop::Utils.error "No #{Noop::Config.dir_path_hiera} directory!" unless Noop::Config.dir_path_hiera.directory? - Noop::Config.dir_path_hiera.find do |hiera_name| - next unless hiera_name.file? - next unless hiera_name.to_s.end_with? '.yaml' - base_dir = hiera_name.dirname.basename - next if base_dir == Noop::Config.dir_name_hiera_override - next if base_dir == Noop::Config.dir_name_globals - @hiera_file_names << hiera_name.relative_path_from(Noop::Config.dir_path_hiera) + error "No #{Noop::Config.dir_path_hiera} directory!" unless Noop::Config.dir_path_hiera.directory? + @hiera_file_names = find_files(Noop::Config.dir_path_hiera, Noop::Config.dir_path_hiera) do |file| + file.to_s.end_with? '.yaml' end - @hiera_file_names end + # Scan the facts directory and gather the list of facts files # @return [Array] def facts_file_names return @facts_file_names if @facts_file_names - @facts_file_names = [] - Noop::Utils.error "No #{Noop::Config.dir_path_facts} directory!" unless Noop::Config.dir_path_facts.directory? - Noop::Config.dir_path_facts.find do |facts_name| - next unless facts_name.file? - next unless facts_name.to_s.end_with? '.yaml' - next if facts_name.dirname.basename == Noop::Config.dir_name_facts_override - @facts_file_names << facts_name.relative_path_from(Noop::Config.dir_path_facts) + error "No #{Noop::Config.dir_path_facts} directory!" unless Noop::Config.dir_path_facts.directory? + @facts_file_names = find_files(Noop::Config.dir_path_facts, Noop::Config.dir_path_facts) do |file| + file.to_s.end_with? '.yaml' end - @facts_file_names end + # Scan the tasks directory and gather the list of task files # @return [Array] def task_file_names return @task_file_names if @task_file_names - @task_file_names = [] - Noop::Utils.error "No #{Noop::Config.dir_path_tasks_local} directory!" unless Noop::Config.dir_path_tasks_local.directory? - Noop::Config.dir_path_tasks_local.find do |task_name| - next unless task_name.file? - next unless task_name.to_s.end_with? '.pp' - @task_file_names << task_name.relative_path_from(Noop::Config.dir_path_tasks_local) + error "No #{Noop::Config.dir_path_tasks_local} directory!" unless Noop::Config.dir_path_tasks_local.directory? + @task_file_names = find_files(Noop::Config.dir_path_tasks_local, Noop::Config.dir_path_tasks_local) do |file| + file.to_s.end_with? '.pp' end - @task_file_names end + # Read the task deployment graph metadata files in the library: + # Find all 'tasks.yaml' files in the puppet directory. + # Read them all to a Hash by their ids. + # Find all 'groups' records and resolve their 'tasks' reference + # by pointing referenced tasks to this group instead. # @return [Hash Hash>] def task_graph_metadata return @task_graph_metadata if @task_graph_metadata @task_graph_metadata = {} - Noop::Utils.error "No #{Noop::Config.dir_path_modules_local} directory!" unless Noop::Config.dir_path_modules_local.directory? + error "No #{Noop::Config.dir_path_modules_local} directory!" unless Noop::Config.dir_path_modules_local.directory? Noop::Config.dir_path_modules_local.find do |task_file| next unless task_file.file? next unless task_file.to_s.end_with? 'tasks.yaml' @@ -91,13 +104,17 @@ module Noop @task_graph_metadata end + # Try to determine the roles each spec should be run in using + # the deployment graph metadata. Take a list of groups or roles + # and form a set of them. # @return [Hash Set>] def assign_spec_to_roles return @assign_spec_to_roles if @assign_spec_to_roles @assign_spec_to_roles = {} task_graph_metadata.values.each do |task_data| - roles = task_data['groups'] or task_data['roles'] - next unless roles.is_a? Array + roles = (task_data['groups'] or task_data['roles'] or task_data['role']) + next unless roles + roles = [roles] unless roles.is_a? Array file_path_manifest = task_data.fetch('parameters', {}).fetch('puppet_manifest', nil) next unless file_path_manifest file_path_manifest = Pathname.new file_path_manifest @@ -110,6 +127,9 @@ module Noop @assign_spec_to_roles end + # Try to determine the roles of each Hiera file. + # Take 'nodes' structure and find 'node_roles' of the current node their. + # Form a set of found values and add root 'role' value if found. # @return [Hash Set>] def assign_hiera_to_roles return @assign_hiera_to_roles if @assign_hiera_to_roles @@ -138,19 +158,29 @@ module Noop @assign_hiera_to_roles end + # Determine Hiera files for each spec file by calculating + # the intersection between their roles sets. + # If the spec file contains '*' role it should be counted + # as all possible roles. + # @return [Hash Pathname] def assign_spec_to_hiera return @assign_spec_to_hiera if @assign_spec_to_hiera @assign_spec_to_hiera = {} - assign_spec_to_roles.each do |file_name_spec, spec_roles| - hiera_files = assign_hiera_to_roles.select do |file_name_hiera, hiera_roles| - roles_intersection = hiera_roles & spec_roles - roles_intersection.any? - end.keys + assign_spec_to_roles.each do |file_name_spec, spec_roles_set| + if spec_roles_set.include? '*' + hiera_files = assign_hiera_to_roles.keys + else + hiera_files = assign_hiera_to_roles.select do |file_name_hiera, hiera_roles_set| + roles_intersection = hiera_roles_set & spec_roles_set + roles_intersection.any? + end.keys + end @assign_spec_to_hiera[file_name_spec] = hiera_files if hiera_files.any? end @assign_spec_to_hiera end + # Read all spec annotations metadata. # @return [Hash Array>] def spec_run_metadata return @spec_run_metadata if @spec_run_metadata @@ -165,7 +195,9 @@ module Noop @spec_run_metadata end + # Parse a spec file to find annotation entries. # @param [Pathname] task_spec + # @return [Hash] def parse_spec_file(task_spec) task_spec_metadata = {} @@ -212,6 +244,8 @@ module Noop task_spec_metadata end + # Split a space or comma separated list of yaml files + # and form an Array of the yaml file names. # @return [Array] def get_list_of_yamls(line) line = line.split /\s*,\s*|\s+/ @@ -222,6 +256,15 @@ module Noop end end + # Determine the list of run records for a spec file: + # Take a list of explicitly defined runs if present. + # Make product of allowed Hiera and facts yaml files to + # form more run records. + # Use the default facts file name if there is none + # is given in the annotation. + # Use the list of Hiera files determined by the intersection of + # deployment graph metadata and Hiera yaml contents using roles + # as a common data. def get_spec_runs(file_name_spec) file_name_spec = Noop::Utils.convert_to_path file_name_spec metadata = spec_run_metadata.fetch file_name_spec, {} @@ -242,38 +285,55 @@ module Noop runs end + # Check if the given element matches this filter + # @param [Array] + def filter_is_matched?(filter, element) + return true unless filter + filter = [filter] unless filter.is_a? Array + filter.any? do |expression| + expression = Regexp.new expression.to_s + expression =~ element.to_s + end + end + + # Use filters to check if this spec file is included + # @return [true,false] def spec_included?(spec) - filter = options[:filter_specs] - return true unless filter - filter = [filter] unless filter.is_a? Array - filter.include? spec + filter_is_matched? options[:filter_specs], spec end + # Use filters to check if this facts file is included + # @return [true,false] def facts_included?(facts) - filter = options[:filter_facts] - return true unless filter - filter = [filter] unless filter.is_a? Array - filter.include? facts + filter_is_matched? options[:filter_facts], facts end + # Use filters to check if this Hiera file is included + # @return [true,false] def hiera_included?(hiera) - filter = options[:filter_hiera] - return true unless filter - filter = [filter] unless filter.is_a? Array - filter.include? hiera + filter_is_matched? options[:filter_hiera], hiera end + # Check if the globals spec should be skipped. + # It should not be skipped only if it's explicitly enabled in the filter. + # @return [true,false] def skip_globals?(file_name_spec) return false unless file_name_spec == Noop::Config.spec_name_globals return true unless options[:filter_specs] not spec_included? file_name_spec end + # Check if the spec is disabled using the annotation + # @return [true,false] def spec_is_disabled?(file_name_spec) file_name_spec = Noop::Utils.convert_to_path file_name_spec spec_run_metadata.fetch(file_name_spec, {}).fetch(:disable, false) end + # Form the final list of Task objects that should be running. + # Take all discovered spec files, get run records for them, + # apply filters to exclude filtered records. + # @return [Array] def task_list return @task_list if @task_list @task_list = [] @@ -293,5 +353,25 @@ module Noop @task_list end + # Loop through all task files and find those that + # do not have a corresponding spec file present + # @return [Array] + def find_tasks_without_specs + task_file_names.reject do |manifest| + spec = Noop::Utils.convert_to_spec manifest + spec_file_names.include? spec + end + end + + # Loop through all spec files and find those that + # do not have a corresponding task file present + # @return [Array] + def find_specs_without_tasks + spec_file_names.reject do |spec| + manifest = Noop::Utils.convert_to_manifest spec + task_file_names.include? manifest + end + end + end end diff --git a/lib/noop/manager/options.rb b/lib/noop/manager/options.rb index aaba688..6058cb6 100644 --- a/lib/noop/manager/options.rb +++ b/lib/noop/manager/options.rb @@ -3,6 +3,8 @@ require 'optparse' module Noop class Manager + # Parse the CLI options + # @return [Hash] def options return @options if @options @options = {} @@ -41,9 +43,6 @@ module Noop opts.on('-R', '--run_failed_tasks', 'Run the task that have previously failed again') do @options[:run_failed_tasks] = true end - opts.on('-M', '--list_missing', 'List all task manifests without a spec file') do - @options[:list_missing] = true - end opts.on('-x', '--xunit_report', 'Save report in xUnit format to a file') do @options[:xunit_report] = true end @@ -64,13 +63,13 @@ module Noop opts.separator 'Filter options:' opts.on('-s', '--specs SPEC1,SPEC2', Array, 'Run only these spec files. Example: "hosts/hosts_spec.rb,apache/apache_spec.rb"') do |specs| - @options[:filter_specs] = import_specs_list specs + @options[:filter_specs] = specs end opts.on('-y', '--yamls YAML1,YAML2', Array, 'Run only these hiera yamls. Example: "controller.yaml,compute.yaml"') do |yamls| - @options[:filter_hiera] = import_yamls_list yamls + @options[:filter_hiera] = yamls end opts.on('-f', '--facts FACTS1,FACTS2', Array, 'Run only these facts yamls. Example: "ubuntu.yaml,centos.yaml"') do |yamls| - @options[:filter_facts] = import_yamls_list yamls + @options[:filter_facts] = yamls end # opts.on('-e', '--examples STR1,STR2', Array, 'Run only these spec examples. Example: "should compile"') do |examples| # @options[:filter_examples] = examples @@ -80,7 +79,7 @@ module Noop opts.on('-c', '--task_console', 'Run PRY console') do ENV['SPEC_TASK_CONSOLE'] = 'YES' end - opts.on('-C', '--rspec_console', 'Run PRY console in the ') do + opts.on('-C', '--rspec_console', 'Run PRY console in the RSpec process') do ENV['SPEC_RSPEC_CONSOLE'] = 'YES' end opts.on('-d', '--task_debug', 'Show framework debug messages') do @@ -126,12 +125,12 @@ module Noop opts.on('-A', '--catalog_show', 'Show catalog content debug output') do ENV['SPEC_CATALOG_SHOW'] = 'YES' end - # opts.on('--catalog_save', 'Save catalog to the files instead of comparing them with the current catalogs') do - # ENV['SPEC_CATALOG_CHECK'] = 'save' - # end - # opts.on('--catalog_check', 'Check the saved catalog against the current one') do - # ENV['SPEC_CATALOG_CHECK'] = 'check' - # end + opts.on('-V', '--catalog_save', 'Save catalog to the files instead of comparing them with the current catalogs') do + ENV['SPEC_CATALOG_CHECK'] = 'save' + end + opts.on('-v', '--catalog_check', 'Check the saved catalog against the current one') do + ENV['SPEC_CATALOG_CHECK'] = 'check' + end # opts.on('--spec_generate', 'Generate specs for catalogs') do # ENV['SPEC_SPEC_GENERATE'] = 'YES' # end @@ -141,30 +140,19 @@ module Noop # opts.on('--spec_coverage', 'Show spec coverage statistics') do # ENV['SPEC_COVERAGE'] = 'YES' # end - # opts.on('--puppet_binary_files', 'Check if Puppet installs binary files') do - # ENV['SPEC_PUPPET_BINARY_FILES'] = 'YES' - # end - # opts.on('--file_resources DIR', 'Save file resources to this dir') do |dir| - # ENV['SPEC_SAVE_FILE_RESOURCES'] = dir - # end + opts.on('--puppet_binary_files', 'Check if Puppet installs binary files') do + ENV['SPEC_PUPPET_BINARY_FILES'] = 'YES' + end + opts.on('--save_file_resources', 'Save file resources list to a report file') do + ENV['SPEC_SAVE_FILE_RESOURCES'] = 'YES' + end end optparse.parse! @options end - def import_specs_list(specs) - specs.map do |spec| - Noop::Utils.convert_to_spec spec - end - end - - def import_yamls_list(yamls) - yamls.map do |yaml| - Noop::Utils.convert_to_yaml yaml - end - end - + # Any default options values can be set here def options_defaults(options) options[:parallel_run] = 0 end diff --git a/lib/noop/manager/report.rb b/lib/noop/manager/report.rb index 18fe87c..d79cbd4 100644 --- a/lib/noop/manager/report.rb +++ b/lib/noop/manager/report.rb @@ -1,60 +1,12 @@ require 'erb' -require 'colorize' -require 'rexml/document' - -# TODO: cli report should use data from tasks_report_structure instead of reimplementing it module Noop class Manager - STATUS_STRING_LENGTH = 8 - - def tasks_report_structure(tasks) - tasks_report = [] - - tasks.each do |task| - task_hash = {} - task_hash[:status] = task.status - task_hash[:name] = task.to_s - task_hash[:description] = task.description - task_hash[:spec] = task.file_name_spec.to_s - task_hash[:hiera] = task.file_name_hiera.to_s - task_hash[:facts] = task.file_name_facts.to_s - task_hash[:task] = task.file_name_manifest.to_s - task_hash[:examples] = [] - - if task.report.is_a? Hash - examples = task.report['examples'] - next unless examples.is_a? Array - examples.each do |example| - example_hash = {} - example_hash[:file_path] = example['file_path'] - example_hash[:line_number] = example['line_number'] - example_hash[:description] = example['description'] - example_hash[:status] = example['status'] - example_hash[:run_time] = example['run_time'] - example_hash[:pending_message] = example['pending_message'] - exception_class = example.fetch('exception', {}).fetch('class', nil) - exception_message = example.fetch('exception', {}).fetch('message', nil) - next unless example_hash[:description] and example_hash[:status] - if exception_class and exception_message - example_hash[:exception_class] = exception_class - example_hash[:exception_message] = exception_message - end - task_hash[:examples] << example_hash - end - - summary = task.report['summary'] - task_hash[:example_count] = summary['example_count'] - task_hash[:failure_count] = summary['failure_count'] - task_hash[:pending_count] = summary['pending_count'] - task_hash[:duration] = summary['duration'] - end - - tasks_report << task_hash - end - tasks_report - end + COLUMN_WIDTH = 8 + # Output a status string for this task. + # Output examples to unless disables. + # @param task [Noop::Task] def output_task_status(task) return if options[:report_only_failed] and task.success? line = task_status_string task @@ -65,6 +17,8 @@ module Noop output_task_examples task unless options[:report_only_tasks] end + # Output examples report for this task + # @param task [Noop::Task] def output_task_examples(task) return unless task.report.is_a? Hash examples = task.report['examples'] @@ -81,36 +35,47 @@ module Noop end end + # Get a colored string with status of this task + # @param task [Noop::Task] + # @return [String] def task_status_string(task) if task.pending? - 'PENDING'.ljust(STATUS_STRING_LENGTH).colorize :blue + 'PENDING'.ljust(COLUMN_WIDTH).colorize :blue elsif task.success? - 'SUCCESS'.ljust(STATUS_STRING_LENGTH).colorize :green + 'SUCCESS'.ljust(COLUMN_WIDTH).colorize :green elsif task.failed? - 'FAILED'.ljust(STATUS_STRING_LENGTH).colorize :red + 'FAILED'.ljust(COLUMN_WIDTH).colorize :red else task.status end end + # Colorize the example status string + # @param status [String] + # @return [String] def example_status_string(status) if status == 'passed' - status.ljust(STATUS_STRING_LENGTH).colorize :green + status.ljust(COLUMN_WIDTH).colorize :green elsif status == 'failed' - status.ljust(STATUS_STRING_LENGTH).colorize :red + status.ljust(COLUMN_WIDTH).colorize :red else - status.ljust(STATUS_STRING_LENGTH).colorize :blue + status.ljust(COLUMN_WIDTH).colorize :blue end end + # Return a string showing if the directory is present. + # @param directory [Pathname] + # @return [String] def directory_check_status_string(directory) if directory.directory? - 'SUCCESS'.ljust(STATUS_STRING_LENGTH).colorize :green + 'SUCCESS'.ljust(COLUMN_WIDTH).colorize :green else - 'FAILED'.ljust(STATUS_STRING_LENGTH).colorize :red + 'FAILED'.ljust(COLUMN_WIDTH).colorize :red end end + # Find the length of the longest spec file name + # @return [Integer] def max_length_spec return @max_length_spec if @max_length_spec @max_length_spec = task_list.map do |task| @@ -118,6 +83,8 @@ module Noop end.max end + # Find the length of the longest Hiera file name + # @return [Integer] def max_length_hiera return @max_length_hiera if @max_length_hiera @max_length_hiera = task_list.map do |task| @@ -125,6 +92,8 @@ module Noop end.max end + # Find the length of the longest facts file name + # @return [Integer] def max_length_facts return @max_length_facts if @max_length_facts @max_length_facts = task_list.map do |task| @@ -132,6 +101,7 @@ module Noop end.max end + # Output a status string with tasks count def output_task_totals count = { :total => 0, @@ -139,24 +109,25 @@ module Noop :pending => 0, } task_list.each do |task| + next unless task.is_a? Noop::Task count[:pending] += 1 if task.pending? count[:failed] += 1 if task.failed? count[:total] += 1 end - output "Tasks: #{count[:total]} Failed: #{count[:failed]} Pending: #{count[:pending]}" + output_stats_string 'Tasks', count[:total], count[:failed], count[:pending] end + # Output a status string with examples count def output_examples_total count = { :total => 0, :failed => 0, :pending => 0, } - task_list.each do |task| - examples = task.report['examples'] - next unless examples.is_a? Array - examples.each do |example| + next unless task.is_a? Noop::Task + next unless task.has_report? + task.report['examples'].each do |example| count[:total] += 1 if example['status'] == 'failed' count[:failed] += 1 @@ -165,50 +136,71 @@ module Noop end end end - output "Examples: #{count[:total]} Failed: #{count[:failed]} Pending: #{count[:pending]}" + output_stats_string 'Examples', count[:total], count[:failed], count[:pending] end - def task_report + # Format a status string of examples or tasks + def output_stats_string(name, total, failed, pending) + line = "#{name.to_s.ljust(COLUMN_WIDTH).colorize :yellow}" + line += " Total: #{total.to_s.ljust(COLUMN_WIDTH).colorize :green}" + line += " Failed: #{failed.to_s.ljust(COLUMN_WIDTH).colorize :red}" + line += " Pending: #{pending.to_s.ljust(COLUMN_WIDTH).colorize :blue}" + output line + end + + # Show the main tasks report + def tasks_report + output Noop::Utils.separator task_list.each do |task| output_task_status task end + output Noop::Utils.separator + tasks_stats + output Noop::Utils.separator + end + + # Show the tasks and examples stats + def tasks_stats output_examples_total unless options[:report_only_tasks] output_task_totals end + # Show report with all defined filters content def show_filters if options[:filter_specs] options[:filter_specs] = [options[:filter_specs]] unless options[:filter_specs].is_a? Array - output "Spec filter: #{options[:filter_specs].join ', '}" + output "Spec filter: #{options[:filter_specs].join(', ').colorize :green}" end if options[:filter_facts] options[:filter_facts] = [options[:filter_facts]] unless options[:filter_facts].is_a? Array - output "Facts filter: #{options[:filter_facts].join ', '}" + output "Facts filter: #{options[:filter_facts].join(', ').colorize :green}" end if options[:filter_hiera] options[:filter_hiera] = [options[:filter_hiera]] unless options[:filter_hiera].is_a? Array - output "Hiera filter: #{options[:filter_hiera].join ', '}" + output "Hiera filter: #{options[:filter_hiera].join(', ').colorize :green}" end if options[:filter_examples] options[:filter_examples] = [options[:filter_examples]] unless options[:filter_examples].is_a? Array - output "Examples filter: #{options[:filter_examples].join ', '}" + output "Examples filter: #{options[:filter_examples].join(', ').colorize :green}" end end + # Show the stats of discovered library objects def show_library template = <<-'eof' -<%= '=' * 80 %> -Tasks discovered: <%= task_file_names.length %> -Specs discovered: <%= spec_file_names.length %> -Hiera discovered: <%= hiera_file_names.length %> -Facts discovered: <%= facts_file_names.length %> -Tasks in graph metadata: <%= task_graph_metadata.length %> -Tasks with spec metadata: <%= spec_run_metadata.length %> -Total tasks to run: <%= task_list.count %> +Tasks discovered: <%= task_file_names.length.to_s.colorize :green %> +Specs discovered: <%= spec_file_names.length.to_s.colorize :green %> +Hiera discovered: <%= hiera_file_names.length.to_s.colorize :green %> +Facts discovered: <%= facts_file_names.length.to_s.colorize :green %> + +Tasks in graph metadata: <%= task_graph_metadata.length.to_s.colorize :yellow %> +Tasks with spec metadata: <%= spec_run_metadata.length.to_s.colorize :yellow %> +Total tasks to run: <%= task_list.count.to_s.colorize :yellow %> eof output ERB.new(template, nil, '-').result(binding) end + # Check the existence of main directories def check_paths paths = [ :dir_path_config, @@ -226,11 +218,46 @@ Total tasks to run: <%= task_list.count %> :dir_path_reports, ] max_length = paths.map { |p| p.to_s.length }.max + paths.each do |path| directory = Noop::Config.send path output "#{directory_check_status_string directory} #{path.to_s.ljust max_length} #{directory}" end end + # Output a list of tasks without a spec file + # and a list of specs without a task file. + def list_missing_tasks_and_specs + tasks_without_specs = find_tasks_without_specs.to_a + specs_without_tasks = find_specs_without_tasks.to_a + if tasks_without_specs.any? + Noop::Utils.output 'There are tasks without specs:'.colorize :red + tasks_without_specs.each do |task| + Noop::Utils.output "#{'*'.colorize :yellow} #{task}" + end + end + if specs_without_tasks.any? + Noop::Utils.output 'There are specs without tasks:'.colorize :red + specs_without_tasks.each do |spec| + Noop::Utils.output "#{'*'.colorize :yellow} #{spec}" + end + end + end + + # Run all diagnostic procedures + def self_check + output Noop::Utils.separator 'Paths' + check_paths + if has_filters? + output Noop::Utils.separator 'Filters' + show_filters + end + output Noop::Utils.separator 'Missing' + list_missing_tasks_and_specs + output Noop::Utils.separator 'Library' + show_library + output Noop::Utils.separator 'End' + end + end end diff --git a/lib/noop/manager/setup.rb b/lib/noop/manager/setup.rb index 31b7d94..15654f6 100644 --- a/lib/noop/manager/setup.rb +++ b/lib/noop/manager/setup.rb @@ -1,59 +1,90 @@ module Noop class Manager + # Get a GEM_HOME either from the environment (using RVM) + # or from the default value (using bundle) + # @return [Pathname] def dir_path_gem_home return Pathname.new ENV['GEM_HOME'] if ENV['GEM_HOME'] dir_name_bundle = Pathname.new 'bundled_gems' Noop::Config.dir_path_workspace + dir_name_bundle end + # Check if bundle command is installed + # @return [true,false] def bundle_installed? `bundle --version` $?.exitstatus == 0 end + # Check if librarian-puppet command is installed + # @return [true,false] def librarian_installed? `librarian-puppet version` $?.exitstatus == 0 end + # Setup bundle in the fixtures repo and bundle for puppet librarian def setup_bundle ENV['GEM_HOME'] = dir_path_gem_home.to_s - Dir.chdir Noop::Config.dir_path_root - bundle_install_and_update - Dir.chdir Noop::Config.dir_path_deployment - bundle_install_and_update + bundle_install_and_update Noop::Config.dir_path_root + bundle_install_and_update Noop::Config.dir_path_deployment Dir.chdir Noop::Config.dir_path_root end + # Run update script to setup external Puppet modules def setup_library ENV['GEM_HOME'] = dir_path_gem_home.to_s - Dir.chdir Noop::Config.dir_path_deployment - update_puppet_modules + update_puppet_modules Noop::Config.dir_path_deployment Dir.chdir Noop::Config.dir_path_root end - def bundle_install_and_update - Noop::Utils.error 'Bundle is not installed!' unless bundle_installed? - Noop::Utils.debug "Starting 'bundle install' in the Gem home: #{ENV['GEM_HOME']}" - Noop::Utils.run 'bundle install' - Noop::Utils.error 'Could not prepare bundle environment!' if $?.exitstatus != 0 - Noop::Utils.debug "Starting 'bundle update' in the Gem home: #{ENV['GEM_HOME']}" - Noop::Utils.run 'bundle update' - Noop::Utils.error 'Could not update bundle environment!' if $?.exitstatus != 0 + # @return [Pathname] + def file_name_gemfile_lock + Pathname.new 'Gemfile.lock' end - # run librarian-puppet to fetch modules as necessary - def update_puppet_modules - Noop::Utils.error 'Puppet Librarian is not installed!' unless librarian_installed? + # Remove the Gem lock file at the given path + # @param root [String,Pathname] + def remove_gemfile_lock(root) + root = Noop::Utils.convert_to_path root + lock_file_path = root + file_name_gemfile_lock + if lock_file_path.file? + debug "Removing Gem lock file: '#{lock_file_path}'" + lock_file_path.unlink + end + end + + # Run bundles install and update actions in the given folder + # @param root [String,Pathname] + def bundle_install_and_update(root) + error 'Bundle is not installed!' unless bundle_installed? + root = Noop::Utils.convert_to_path root + remove_gemfile_lock root + Dir.chdir root or error "Could not chdir to: #{root}" + debug "Starting 'bundle install' at: '#{root}' with the Gem home: '#{ENV['GEM_HOME']}'" + Noop::Utils.run 'bundle install' + error 'Could not prepare bundle environment!' if $?.exitstatus != 0 + debug "Starting 'bundle update' at: '#{root}' with the Gem home: '#{ENV['GEM_HOME']}'" + Noop::Utils.run 'bundle update' + error 'Could not update bundle environment!' if $?.exitstatus != 0 + end + + # Run librarian-puppet to fetch modules as + # necessary modules at the given folder + # @param root [String,Pathname] + def update_puppet_modules(root) + error 'Puppet Librarian is not installed!' unless librarian_installed? + root = Noop::Utils.convert_to_path root + Dir.chdir root or error "Could not chdir to: #{root}" command = './update_modules.sh -v' command = command + ' -b' if options[:bundle_exec] command = command + ' -r' if options[:reset_librarian_puppet] - Noop::Utils.debug 'Starting update_modules script' + debug 'Starting update_modules script' Noop::Utils.run command - Noop::Utils.error 'Unable to update upstream puppet modules using librarian-puppet!' if $?.exitstatus != 0 - Noop::Utils.debug 'Finished update_modules script' + error 'Unable to update upstream puppet modules using librarian-puppet!' if $?.exitstatus != 0 + debug 'Finished update_modules script' end end diff --git a/lib/noop/manager/xunit.rb b/lib/noop/manager/xunit.rb index eac79a6..bfb1281 100644 --- a/lib/noop/manager/xunit.rb +++ b/lib/noop/manager/xunit.rb @@ -1,8 +1,60 @@ +require 'rexml/document' + module Noop class Manager - def xunit_report(tasks) - tasks_report = tasks_report_structure tasks - return unless tasks_report.is_a? Array + + # Generate a data structure that will be used to create the xUnit report + # @return [Array] + def tasks_report_structure + tasks_report = [] + + task_list.each do |task| + task_hash = {} + task_hash[:status] = task.status + task_hash[:name] = task.to_s + task_hash[:description] = task.description + task_hash[:spec] = task.file_name_spec.to_s + task_hash[:hiera] = task.file_name_hiera.to_s + task_hash[:facts] = task.file_name_facts.to_s + task_hash[:task] = task.file_name_manifest.to_s + task_hash[:examples] = [] + + if task.report.is_a? Hash + examples = task.report['examples'] + next unless examples.is_a? Array + examples.each do |example| + example_hash = {} + example_hash[:file_path] = example['file_path'] + example_hash[:line_number] = example['line_number'] + example_hash[:description] = example['description'] + example_hash[:status] = example['status'] + example_hash[:run_time] = example['run_time'] + example_hash[:pending_message] = example['pending_message'] + exception_class = example.fetch('exception', {}).fetch('class', nil) + exception_message = example.fetch('exception', {}).fetch('message', nil) + next unless example_hash[:description] and example_hash[:status] + if exception_class and exception_message + example_hash[:exception_class] = exception_class + example_hash[:exception_message] = exception_message + end + task_hash[:examples] << example_hash + end + + summary = task.report['summary'] + task_hash[:example_count] = summary['example_count'] + task_hash[:failure_count] = summary['failure_count'] + task_hash[:pending_count] = summary['pending_count'] + task_hash[:duration] = summary['duration'] + end + + tasks_report << task_hash + end + tasks_report + end + + # Generate xUnit XML report text + # @return [String] + def xunit_report document = REXML::Document.new declaration = REXML::XMLDecl.new declaration.encoding = 'UTF-8' @@ -13,7 +65,7 @@ module Noop failures = 0 task_id = 0 - tasks_report.each do |task| + tasks_report_structure.each do |task| testsuite = testsuites.add_element 'testsuite' testsuite.add_attribute 'id', task_id task_id += 1 @@ -67,19 +119,25 @@ module Noop document.to_s end + # xUnit report file name + # @return [Pathname] def file_name_xunit_report Pathname.new 'report.xml' end + # Full path to the xUnit report file + # @return [Pathname] def file_path_xunit_report Noop::Config.dir_path_reports + file_name_xunit_report end + # Write the xUnit report to the file + # @return [void] def save_xunit_report + debug "Saving xUnit XML report file to: #{file_path_xunit_report.to_s}" File.open(file_path_xunit_report.to_s, 'w') do |file| - file.puts xunit_report task_list + file.puts xunit_report end - Noop::Utils.debug "xUnit XML report was saved to: #{file_path_xunit_report.to_s}" end end diff --git a/lib/noop/task.rb b/lib/noop/task.rb index bd84a90..421cfed 100644 --- a/lib/noop/task.rb +++ b/lib/noop/task.rb @@ -8,3 +8,5 @@ require_relative 'task/run' require_relative 'task/overrides' require_relative 'task/catalog' require_relative 'task/helpers' +require_relative 'task/files' +require_relative 'task/report' diff --git a/lib/noop/task/base.rb b/lib/noop/task/base.rb index 2f98746..1709f1a 100644 --- a/lib/noop/task/base.rb +++ b/lib/noop/task/base.rb @@ -8,50 +8,96 @@ module Noop self.pid = Process.pid self.thread = Thread.current.object_id @parallel = false - Noop::Utils.warning "#{self}: Validation is failed!" unless valid? end attr_accessor :parallel attr_accessor :pid attr_accessor :thread attr_accessor :status + attr_accessor :valid + # Check if this task's configuration is valid + # @return [true,false] + def valid? + validate unless valid.is_a? TrueClass or valid.is_a? FalseClass + valid + end + + # @return [true,false] def success? status == :success end + # @return [true,false] def failed? status == :failed end + # @return [true,false] def pending? status == :pending end + # Write a debug message to the logger + # @return [void] + def debug(message) + Noop::Config.log.debug message + end + + # Output a message to the console + # @return [void] + def output(message) + Noop::Utils.output message + end + + # Write an error message to the log + # and raise the exception + # @return [void] + def error(message) + Noop::Utils.error message + end + + # Write a warning message to the log + # @return [void] + def warning(message) + Noop::Utils.warning message + end + # @return [true,false] def parallel_run? parallel end # @return [true,false] - def valid? - unless file_path_spec.exist? - Noop::Utils.warning "No spec file: #{file_path_spec}!" - return false + def validate + if file_name_spec_set? + unless file_present_spec? + warning "No spec file: #{file_path_spec}!" + self.valid = false + return valid + end + else + warning 'Spec file is not set for this task!' + self.valid = false + return valid end - unless file_path_manifest.exist? - Noop::Utils.warning "No task file: #{file_path_manifest}!" - return false + + unless file_present_manifest? + warning "No task file: #{file_path_manifest}!" + self.valid = false + return valid end - unless file_path_hiera.exist? - Noop::Utils.warning "No hiera file: #{file_path_hiera}!" - return false + unless file_present_hiera? + warning "No hiera file: #{file_path_hiera}!" + self.valid = false + return valid end - unless file_path_facts.exist? - Noop::Utils.error "No facts file: #{file_path_hiera}!" - return false + unless file_present_facts? + warning "No facts file: #{file_path_facts}!" + self.valid = false + return valid end - true + self.valid = true end # @return [String] @@ -59,9 +105,10 @@ module Noop "Task[#{file_base_spec}]" end + # @return [String] def description message = '' - message += "Task: #{file_name_manifest}" + message += "Manifest: #{file_name_manifest}" message += " Spec: #{file_name_spec}" message += " Hiera: #{file_name_hiera}" message += " Facts: #{file_name_facts}" @@ -69,6 +116,7 @@ module Noop message end + # @return [String] def process_info message = '' message + "Object: #{object_id}" @@ -81,5 +129,6 @@ module Noop def inspect "Task[#{description}]" end + end end diff --git a/lib/noop/task/catalog.rb b/lib/noop/task/catalog.rb index bca4713..12b01d3 100644 --- a/lib/noop/task/catalog.rb +++ b/lib/noop/task/catalog.rb @@ -2,35 +2,8 @@ require 'erb' module Noop class Task - # @return [String] - def status_report(context) - task = context.task - template = <<-'eof' -Facts: <%= task.file_path_facts %> -Hiera: <%= task.file_path_hiera %> -Spec: <%= task.file_path_spec %> -Manifest: <%= task.file_path_manifest %> -Node: <%= task.hiera_lookup 'fqdn' or '?' %> -Role: <%= task.hiera_lookup 'role' or '?' %> - -Puppet: <%= Puppet.version %> -Ruby: <%= RUBY_VERSION %> - -Hiera hierarchy: -<% task.hiera_hierarchy.each do |element| -%> -* <%= element %> -<% end -%> - -Facts hierarchy: -<% task.facts_hierarchy.reverse.each do |element| -%> -* <%= element %> -<% end -%> - eof - ERB.new(template, nil, '-').result(binding) - end - - # dumps the entire catalog structure to the text + # Dumps the entire catalog structure to the text # representation in the Puppet language # @param context [Object] the context from the rspec test # @param resources_filter [Array] the list of resources to dump. Dump all resources if not given @@ -56,7 +29,7 @@ Facts hierarchy: text end - # takes a parameter value and formats it to the literal value + # Takes a parameter value and formats it to the literal value # that could be placed in the Puppet manifest # @param value [String, Array, Hash, true, false, nil] # @return [String] @@ -88,7 +61,7 @@ Facts hierarchy: end end - # take a resource object and generate a manifest representation of it + # Take a resource object and generate a manifest representation of it # in the Puppet language. Replaces "to_manifest" Puppet function which # is not working correctly. # @param resource [Puppet::Resource] @@ -131,7 +104,7 @@ Facts hierarchy: clear_data.join "\n" end - # check if two resources have same type and title + # Check if two resources have same type and title # @param res1 [Puppet::Resource] # @param res2 [Puppet::Resource] # @return [TrueClass, False,Class] @@ -141,5 +114,52 @@ Facts hierarchy: res1 == res2 end + # @return [Pathname] + def dir_name_catalogs + Pathname.new 'catalogs' + end + + # @return [Pathname] + def dir_path_catalogs + Noop::Config.dir_path_root + dir_name_catalogs + end + + # @return [Pathname] + def file_name_task_catalog + Noop::Utils.convert_to_path "#{file_name_base_task_report}.pp" + end + + # @return [Pathname] + def file_path_task_catalog + dir_path_catalogs + file_name_task_catalog + end + + # Write the catalog file of this task + # using the data from RSpec context + # @param context [Object] the context from the rspec test + # @return [void] + def file_write_task_catalog(context) + dir_path_catalogs.mkpath + error "Catalog directory '#{dir_path_catalogs}' doesn't exist!" unless dir_path_catalogs.directory? + debug "Writing catalog file: #{file_path_task_catalog}" + File.open(file_path_task_catalog.to_s, 'w') do |file| + file.puts catalog_dump context + end + end + + # Check if the catalog file exists for this task + # @return [true,false] + def file_present_task_catalog? + file_path_task_catalog.file? + end + + # Read the catalog file of this task + # @return [String] + def file_read_task_catalog + return unless file_present_task_catalog? + debug "Reading catalog file: #{file_path_task_catalog}" + file_path_task_catalog.read + end + end end diff --git a/lib/noop/task/facts.rb b/lib/noop/task/facts.rb index 0ce3a08..8ccef34 100644 --- a/lib/noop/task/facts.rb +++ b/lib/noop/task/facts.rb @@ -60,6 +60,7 @@ module Noop file_paths end + # @return [void] def add_host_names(facts_data) hostname = hiera_lookup 'node_name' fqdn = hiera_lookup 'fqdn' @@ -86,10 +87,12 @@ module Noop alias :ubuntu_facts :facts_data alias :centos_facts :facts_data + # @return [String,nil] def hostname facts_data[:hostname] end + # @return [String,nil] def fqdn facts_data[:fqdn] end diff --git a/lib/noop/task/files.rb b/lib/noop/task/files.rb new file mode 100644 index 0000000..090fc20 --- /dev/null +++ b/lib/noop/task/files.rb @@ -0,0 +1,100 @@ +module Noop + class Task + # @return [Pathname] + def dir_name_file_reports + Pathname.new 'files' + end + + # @return [Pathname] + def dir_path_file_reports + Noop::Config.dir_path_reports + dir_name_file_reports + end + + # @return [Pathname] + def file_name_file_report + Noop::Utils.convert_to_path "#{file_name_base_task_report}.yaml" + end + + # @return [Pathname] + def file_path_file_report + dir_path_file_reports + file_name_file_report + end + + # @return [Array] + def find_file_resources(context) + catalog = context.subject + catalog = catalog.call if catalog.is_a? Proc + catalog.resources.select do |resource| + resource.type == 'File' + end + end + + # @return [Hash] + def catalog_file_report_structure(context) + files = {} + find_file_resources(context).each do |resource| + next unless %w(present file directory).include? resource[:ensure] or not resource[:ensure] + if resource[:source] + content = resource[:source] + elsif resource[:content] + content = 'TEMPLATE' + else + content = nil + end + next unless content + files[resource[:path]] = content + end + files + end + + # @return [String] + def catalog_file_report_template(binding) + template = <<-'eos' +<% if binary_files.any? -%> +You have <%= binary_files.length -%> files that are either binary or init.d scripts: +<% binary_files.each do |file| -%> +* <%= file %> +<% end -%> +<% end -%> +<% if downloaded_files.any? -%> +You are downloading <%= downloaded_files.length -%> files using File resource's source property: +<% downloaded_files.each do |file| -%> +* <%= file %> +<% end -%> +<% end -%> +eos + ERB.new(template, nil, '-').result(binding) + end + + # @return [void] + def catalog_file_resources_check(context) + binary_files_regexp = %r{^/bin|^/usr/bin|^/usr/local/bin|^/usr/sbin|^/sbin|^/usr/lib|^/usr/share|^/etc/init.d|^/usr/local/sbin|^/etc/rc\S\.d} + binary_files = [] + downloaded_files = [] + find_file_resources(context).each do |resource| + next unless %w(present file directory).include? resource[:ensure] or not resource[:ensure] + file_path = resource[:path] or resource[:title] + file_source = resource[:source] + binary_files << file_path if file_path =~ binary_files_regexp + downloaded_files << file_path if file_source + end + if binary_files.any? or downloaded_files.any? + output Noop::Utils.separator + output catalog_file_report_template(binding) + output Noop::Utils.separator + error 'Puppet is installing files that should be packed to the Fuel package!' + end + end + + # @return [void] + def catalog_file_report_write(context) + dir_path_file_reports.mkpath + error "File report directory '#{dir_path_file_reports}' doesn't exist!" unless dir_path_file_reports.directory? + debug "Saving File resources list file to: #{file_path_file_report.to_s}" + File.open(file_path_file_report.to_s, 'w') do |file| + YAML.dump catalog_file_report_structure(context), file + end + end + + end +end diff --git a/lib/noop/task/globals.rb b/lib/noop/task/globals.rb index 5611121..143eeb0 100644 --- a/lib/noop/task/globals.rb +++ b/lib/noop/task/globals.rb @@ -13,10 +13,10 @@ module Noop end def write_file_globals(content) + debug "Saving Globals YAML file to: '#{file_path_globals.to_s}'" File.open(file_path_globals.to_s, 'w') do |file| file.write content end - Noop::Utils.debug "Globals YAML saved to: '#{file_path_globals.to_s}'" end # @return [Pathname] diff --git a/lib/noop/task/helpers.rb b/lib/noop/task/helpers.rb index a1169f2..659ef53 100644 --- a/lib/noop/task/helpers.rb +++ b/lib/noop/task/helpers.rb @@ -1,46 +1,77 @@ module Noop class Task + # Extract the parameter or property of a Puppet resource in the catalog + # @param context [RSpec::ExampleGroup] The 'self' of the RSpec example group + # @param resource_type [String] Name of the resource type + # @param resource_name [String] Title of the resource + # @param parameter [String] Parameter name + # @return [Object] def resource_parameter_value(context, resource_type, resource_name, parameter) catalog = context.subject catalog = catalog.call if catalog.is_a? Proc resource = catalog.resource resource_type, resource_name - fail "No resource type: '#{resource_type}' name: '#{resource_name}' in the catalog!" unless resource + error "No resource type: '#{resource_type}' name: '#{resource_name}' in the catalog!" unless resource resource[parameter.to_sym] end - # save the current puppet scope + # Save the current puppet scope + # @param value [Puppet::Scope] def puppet_scope=(value) @puppet_scope = value end + # The saved Puppet scope to run functions in + # Or the newly generated scope. + # @return [Puppet::Scope] def puppet_scope return @puppet_scope if @puppet_scope PuppetlabsSpec::PuppetInternals.scope end - # load a puppet function if it's not already loaded + # Load a puppet function if it's not already loaded + # @param name [String] Function name def puppet_function_load(name) name = name.to_sym unless name.is_a? Symbol Puppet::Parser::Functions.autoloader.load name end - # call a puppet function and return it's value + # Call a puppet function and return it's value + # @param name [String] Function name + # @param *args [Object] Function parameters + # @return [Object] def puppet_function(name, *args) name = name.to_sym unless name.is_a? Symbol puppet_function_load name - fail "Could not load Puppet function '#{name}'!" unless puppet_scope.respond_to? "function_#{name}".to_sym + error "Could not load Puppet function '#{name}'!" unless puppet_scope.respond_to? "function_#{name}".to_sym puppet_scope.send "function_#{name}".to_sym, args end - # take a variable value from the saved puppet scope + # Take a variable value from the saved puppet scope + # @param name [String] variable name def lookupvar(name) puppet_scope.lookupvar name end + alias :variable :lookupvar - # convert resource catalog to a RAL catalog + # Load a class from the Puppet modules into the current scope + # It can be used to extract values from 'params' classes like this: + # Noop.load_class 'nova::params' + # Noop.variable 'nova::params::common_package_name' + # => 'openstack-nova-common' + # These values can be later used in the spec examples. + # Note, that the loaded class will not be found in the spec's catalog + # object, but can be found here: Noop.puppet_scope.catalog + # @param class_name [String] + def puppet_class_include(class_name) + class_name = class_name.to_s + puppet_function 'include', class_name unless Noop.puppet_scope.catalog.classes.include? class_name + end + + # Convert resource catalog to a RAL catalog # and run both "generate" functions for each resource # that has it and then add results to the catalog + # @param context [RSpec::ExampleGroup] The 'self' of the RSpec example group # @return def create_ral_catalog(context) catalog = context.subject diff --git a/lib/noop/task/hiera.rb b/lib/noop/task/hiera.rb index d2bcaf4..dd9efc7 100644 --- a/lib/noop/task/hiera.rb +++ b/lib/noop/task/hiera.rb @@ -9,6 +9,7 @@ module Noop @file_name_hiera end + # @return [Pathname] def file_name_hiera=(value) return if value.nil? @file_name_hiera = Noop::Utils.convert_to_path value @@ -141,6 +142,7 @@ module Noop data = hiera key, nil, resolution_type path_lookup.call data, path, default end + alias :hiera_dir :hiera_structure end end diff --git a/lib/noop/task/overrides.rb b/lib/noop/task/overrides.rb index 35f0a6b..d07e8fc 100644 --- a/lib/noop/task/overrides.rb +++ b/lib/noop/task/overrides.rb @@ -1,19 +1,24 @@ module Noop class Task + # Setup all needed override functions def setup_overrides puppet_default_settings - hiera_config_override puppet_debug_override if ENV['SPEC_PUPPET_DEBUG'] - setup_manifest puppet_resource_scope_override + return unless file_name_spec_set? + hiera_config_override + setup_manifest end + # Set the current module path and the manifest file + # to run in this RSpec session def setup_manifest RSpec.configuration.manifest = file_path_manifest.to_s RSpec.configuration.module_path = Noop::Config.dir_path_modules_local.to_s RSpec.configuration.manifest_dir = Noop::Config.dir_path_tasks_local.to_s end + # Override Hiera configuration in the Puppet objects def hiera_config_override class << HieraPuppet def hiera @@ -48,6 +53,7 @@ module Noop Hiera::Config.config = hiera_config end + # Ask Puppet to save the current scope reference to the task instance def puppet_resource_scope_override Puppet::Parser::Resource.module_eval do def initialize(*args) @@ -60,6 +66,7 @@ module Noop end end + # Divert Puppet logs to the console def puppet_debug_override Puppet::Util::Log.level = :debug Puppet::Util::Log.newdestination(:console) diff --git a/lib/noop/task/report.rb b/lib/noop/task/report.rb new file mode 100644 index 0000000..2693479 --- /dev/null +++ b/lib/noop/task/report.rb @@ -0,0 +1,100 @@ +module Noop + class Task + attr_accessor :report + + # Generate the report of the currently using files in this spec + # @return [String] + def status_report(context) + task = context.task + template = <<-'eof' +Facts: <%= task.file_path_facts %> +Hiera: <%= task.file_path_hiera %> +Spec: <%= task.file_path_spec %> +Manifest: <%= task.file_path_manifest %> + +Node: <%= task.hiera_lookup 'fqdn' or '?' %> +Role: <%= task.hiera_lookup 'role' or '?' %> + +Hiera hierarchy: +<% task.hiera_hierarchy.each do |element| -%> +* <%= element %> +<% end -%> + +Facts hierarchy: +<% task.facts_hierarchy.reverse.each do |element| -%> +* <%= element %> +<% end -%> + eof + ERB.new(template, nil, '-').result(binding) + end + + # Get a loaded gem version + # @return [String,nil] + def gem_version(gem) + gem = gem.to_s + return unless Object.const_defined? 'Gem' + return unless Gem.loaded_specs.is_a? Hash + return unless Gem.loaded_specs[gem].is_a? Gem::Specification + Gem.loaded_specs[gem].version + end + + # Gem a report about RSpec gems versions + # @return [String] + def gem_versions_report + versions = "Ruby version: #{RUBY_VERSION}" + %w(puppet rspec rspec-puppet rspec-puppet-utils puppetlabs_spec_helper).each do |gem| + version = gem_version gem + versions += "\n'#{gem}' gem version: #{version}"if version + end + versions + end + + # Load a report file of this task if it's present + def file_load_report_json + self.report = file_data_report_json + end + + # Check if this task has report loaded + # @return [true,false] + def has_report? + report.is_a? Hash and report['examples'].is_a? Array + end + + # @return [Pathname] + def file_name_report_json + Noop::Utils.convert_to_path "#{file_name_base_task_report}.json" + end + + # @return [Pathname] + def file_path_report_json + Noop::Config.dir_path_reports + file_name_report_json + end + + # @return [Hash] + def file_data_report_json + return unless file_present_report_json? + file_data = nil + begin + # debug "Reading report file: #{file_path_report_json}" + file_content = File.read file_path_report_json.to_s + file_data = JSON.load file_content + return unless file_data.is_a? Hash + rescue + debug "Error parsing report file: #{file_path_report_json}" + nil + end + file_data + end + + # Remove the report file + def file_remove_report_json + #debug "Removing report file: #{file_path_report_json}" + file_path_report_json.unlink if file_present_report_json? + end + + # @return [true,false] + def file_present_report_json? + file_path_report_json.exist? + end + end +end diff --git a/lib/noop/task/run.rb b/lib/noop/task/run.rb index 6233844..6bd1981 100644 --- a/lib/noop/task/run.rb +++ b/lib/noop/task/run.rb @@ -2,23 +2,24 @@ require 'json' module Noop class Task + # Run the actual spec of this task. + # It will execute the rspec command and will manage report files. def run + validate + error 'Validation of this task have failed!' unless valid? return unless pending? self.pid = Process.pid self.thread = Thread.current.object_id - Noop::Utils.debug "RUN: #{self.inspect}" + debug "RUN: #{self.inspect}" file_remove_report_json rspec_command_run file_load_report_json determine_task_status - Noop::Utils.debug "FINISH: #{self.inspect}" + debug "FINISH: #{self.inspect}" status end - def file_load_report_json - self.report = file_data_report_json - end - + # Set the status string of the task according to run results def set_status_value(value) if value.is_a? TrueClass self.status = :success @@ -29,6 +30,7 @@ module Noop end end + # Try to determine the task status based on the report data def determine_task_status if report.is_a? Hash failures = report.fetch('summary', {}).fetch('failure_count', nil) @@ -39,45 +41,17 @@ module Noop status end - # @return [Pathname] - def file_name_report_json - Noop::Utils.convert_to_path "#{file_name_task_extension.sub_ext ''}_#{file_base_hiera}_#{file_base_facts}.json" - end - - # @return [Pathname] - def file_path_report_json - Noop::Config.dir_path_reports + file_name_report_json - end - - # @return [Hash] - def file_data_report_json - return unless file_present_report_json? - file_data = nil - begin - file_content = File.read file_path_report_json.to_s - file_data = JSON.load file_content - return unless file_data.is_a? Hash - rescue - nil - end - file_data - end - - def file_remove_report_json - file_path_report_json.unlink if file_present_report_json? - end - - # @return [true,false] - def file_present_report_json? - file_path_report_json.exist? - end - + # Additional RSpec options def rspec_options options = '--color --tty' options += ' --format documentation' unless parallel_run? options end + # Run the RSpec command and pass Hiera, facts and spec files names + # using the environment variables. + # Use bundler if it's enabled. + # Set the task status according to the RSpec exit code. # @return [true,false] def rspec_command_run environment = { @@ -89,10 +63,13 @@ module Noop command = "bundle exec #{command}" if ENV['SPEC_BUNDLE_EXEC'] Dir.chdir Noop::Config.dir_path_root success = Noop::Utils.run environment, command + if success.nil? + debug 'RSpec command is not found!' + success = false + end set_status_value success success end - attr_accessor :report end end diff --git a/lib/noop/task/spec.rb b/lib/noop/task/spec.rb index 64463ed..492f565 100644 --- a/lib/noop/task/spec.rb +++ b/lib/noop/task/spec.rb @@ -4,9 +4,24 @@ module Noop def file_name_spec return @file_name_spec if @file_name_spec self.file_name_spec = Noop::Utils.path_from_env 'SPEC_FILE_NAME' + error 'The spec file name is not set for this task!' unless file_name_spec_set? @file_name_spec end + # Check if the spec name for this spec is set + # @return [true,false] + def file_name_spec_set? + not @file_name_spec.nil? + end + + # Check if the currently running spec is the same as the given one + # @return [true,false] + def current_spec_is?(spec) + return false unless file_name_spec_set? + spec = Noop::Utils.convert_to_spec spec + file_name_spec == spec + end + # @return [Pathname] def file_base_spec Noop::Utils.convert_to_path(file_name_spec.to_s.gsub /_spec\.rb$/, '') @@ -35,14 +50,24 @@ module Noop end # @return [true,false] - def file_present_spec + def file_present_spec? file_path_spec.readable? end + # @return [true,false] + def file_present_manifest? + file_path_manifest.readable? + end + # @return [Pathname] def file_name_task_extension Noop::Utils.convert_to_path(file_base_spec.to_s.gsub('/', '-') + '.yaml') end + # @return [Pathname] + def file_name_base_task_report + Noop::Utils.convert_to_path("#{file_name_task_extension.sub_ext ''}_#{file_base_hiera}_#{file_base_facts}") + end + end end diff --git a/lib/noop/utils.rb b/lib/noop/utils.rb index 5c66391..0906f99 100644 --- a/lib/noop/utils.rb +++ b/lib/noop/utils.rb @@ -57,7 +57,7 @@ module Noop end def self.run(*args) - debug "CMD: #{args.inspect} PWD: #{Dir.pwd}" + # debug "CMD: #{args.inspect} PWD: #{Dir.pwd}" system *args end @@ -75,7 +75,19 @@ module Noop def self.error(message) Noop::Config.log.fatal message - exit(1) + fail message + end + + def self.output(message) + puts message + end + + def self.separator(title=nil) + if title + "=< #{title} >=".ljust 70, '=' + else + '=' * 70 + end end end end diff --git a/reports/files/.gitignore b/reports/files/.gitignore new file mode 100644 index 0000000..1e82fc7 --- /dev/null +++ b/reports/files/.gitignore @@ -0,0 +1 @@ +*.yaml diff --git a/requirements.txt b/requirements.txt index ed06cfc..2ac693b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ sphinx cloud_sptheme pbr +pip diff --git a/spec/lib/task_spec.rb b/spec/lib/task_spec.rb index 751c03f..d02e7d8 100644 --- a/spec/lib/task_spec.rb +++ b/spec/lib/task_spec.rb @@ -47,7 +47,7 @@ describe Noop::Task do end it 'should have valid?' do - is_expected.not_to be_valid + is_expected.to respond_to(:valid?) end it 'should have to_s' do @@ -55,7 +55,7 @@ describe Noop::Task do end it 'should have inspect' do - expect(subject.inspect).to eq 'Task[Task: my/test.pp Spec: my/test_spec.rb Hiera: novanet-primary-controller.yaml Facts: ubuntu.yaml Status: pending]' + expect(subject.inspect).to eq 'Task[Manifest: my/test.pp Spec: my/test_spec.rb Hiera: novanet-primary-controller.yaml Facts: ubuntu.yaml Status: pending]' end end @@ -199,4 +199,53 @@ describe Noop::Task do end end + context 'validation' do + context 'valid task' do + before(:each) do + allow(subject).to receive(:file_present_spec?).and_return true + allow(subject).to receive(:file_present_manifest?).and_return true + allow(subject).to receive(:file_present_hiera?).and_return true + allow(subject).to receive(:file_present_facts?).and_return true + end + + it 'should be valid' do + subject.validate + is_expected.to be_valid + end + + end + + context 'spec is not set' do + subject do + Noop::Task.new + end + + before(:each) do + allow(Noop::Utils).to receive(:warning) + end + + it 'should report unset spec' do + is_expected.not_to be_file_name_spec_set + end + + it 'should not be valid' do + subject.validate + is_expected.not_to be_valid + end + end + + context 'spec is set but missing' do + + it 'should NOT report unset spec' do + is_expected.to be_file_name_spec_set + end + + it 'should not be valid' do + subject.validate + is_expected.not_to be_valid + end + end + + end + end diff --git a/spec/shared-examples.rb b/spec/shared-examples.rb index 697d1e0..0eea5d0 100644 --- a/spec/shared-examples.rb +++ b/spec/shared-examples.rb @@ -1,20 +1,51 @@ +begin + require_relative 'hosts/common.rb' +rescue LoadError + nil +end + shared_examples 'compile' do it { is_expected.to compile } end shared_examples 'show_catalog' do it 'shows catalog contents' do - puts '=' * 80 - puts Noop.task.catalog_dump self - puts '=' * 80 + Noop::Utils.output Noop::Utils.separator + Noop::Utils.output Noop.task.catalog_dump self + Noop::Utils.output Noop::Utils.separator end end shared_examples 'status' do it 'shows status' do - puts '=' * 80 - puts Noop.task.status_report self - puts '=' * 80 + Noop::Utils.output Noop::Utils.separator + Noop::Utils.output Noop.task.status_report self + Noop::Utils.output Noop::Utils.separator + Noop::Utils.output Noop.task.gem_versions_report + Noop::Utils.output Noop::Utils.separator + end +end + +shared_examples 'files_installed_by_puppet' do + it 'should check that binary files are not installed by this task' do + Noop.catalog_file_resources_check self + end +end + +shared_examples 'save_files_list' do + it 'should save the list of File resources to the file' do + Noop.catalog_file_report_write self + end +end + +shared_examples 'saved_catalog' do + it 'should save the current task catalog to the file', :if => (ENV['SPEC_CATALOG_CHECK'] == 'save') do + Noop.file_write_task_catalog self + end + it 'should check the current task catalog against the saved one', :if => (ENV['SPEC_CATALOG_CHECK'] == 'check') do + saved_catalog = Noop.preprocess_catalog_data Noop.file_read_task_catalog + current_catalog = Noop.preprocess_catalog_data Noop.catalog_dump self + expect(saved_catalog).to eq current_catalog end end @@ -32,6 +63,7 @@ def run_test(manifest_file, *args) Noop::Config.log.progname = 'noop_spec' Noop::Utils.debug "RSPEC: #{Noop.task.inspect}" + Noop.setup_overrides include FuelRelationshipGraphMatchers @@ -68,6 +100,9 @@ def run_test(manifest_file, *args) include_examples 'status' if ENV['SPEC_SHOW_STATUS'] include_examples 'show_catalog' if ENV['SPEC_CATALOG_SHOW'] include_examples 'console' if ENV['SPEC_RSPEC_CONSOLE'] + include_examples 'files_installed_by_puppet' if ENV['SPEC_PUPPET_BINARY_FILES'] + include_examples 'save_files_list' if ENV['SPEC_SAVE_FILE_RESOURCES'] + include_examples 'saved_catalog' if ENV['SPEC_CATALOG_CHECK'] begin include_examples 'catalog' @@ -75,6 +110,12 @@ def run_test(manifest_file, *args) true end + begin + it_behaves_like 'common' + rescue ArgumentError + true + end + yield self if block_given? end