From cf77ef1379c4a93143f6a89db7a56037cc192436 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 14 Sep 2016 12:20:26 -0700 Subject: [PATCH] (PDOC-63) Add specs to test the new implementation. This commit adds specs to cover parts of the new implementation of Puppet Strings. --- Rakefile | 17 +- .../test/lib/puppet/functions/4x_function.rb | 5 + .../lib/puppet/parser/functions/function3x.rb | 3 + .../fixtures/modules/test/manifests/init.pp | 27 + .../test/manifests/triple_nested_classes.pp | 27 + .../fixtures/modules/test/metadata.json | 6 + acceptance/running_strings_generate.rb | 28 + .../yard/handlers/puppet/base.rb | 2 - spec/acceptance/running_strings_yardoc.rb | 28 - spec/fixtures/unit/json/output.json | 348 ++++++++++ spec/spec_helper.rb | 16 +- spec/spec_helper_acceptance.rb | 5 +- spec/unit/puppet-strings/json_spec.rb | 132 ++++ .../handlers/puppet/class_handler_spec.rb | 155 +++++ .../puppet/defined_type_handler_spec.rb | 155 +++++ .../handlers/puppet/function_handler_spec.rb | 168 +++++ .../handlers/ruby/function_handler_spec.rb | 613 ++++++++++++++++++ .../handlers/ruby/provider_handler_spec.rb | 62 ++ .../yard/handlers/ruby/type_handler_spec.rb | 126 ++++ .../yard/parsers/puppet/parser_spec.rb | 171 +++++ 20 files changed, 2045 insertions(+), 49 deletions(-) create mode 100644 acceptance/fixtures/modules/test/lib/puppet/functions/4x_function.rb create mode 100644 acceptance/fixtures/modules/test/lib/puppet/parser/functions/function3x.rb create mode 100644 acceptance/fixtures/modules/test/manifests/init.pp create mode 100644 acceptance/fixtures/modules/test/manifests/triple_nested_classes.pp create mode 100644 acceptance/fixtures/modules/test/metadata.json create mode 100644 acceptance/running_strings_generate.rb delete mode 100644 spec/acceptance/running_strings_yardoc.rb create mode 100644 spec/fixtures/unit/json/output.json create mode 100644 spec/unit/puppet-strings/json_spec.rb create mode 100644 spec/unit/puppet-strings/yard/handlers/puppet/class_handler_spec.rb create mode 100644 spec/unit/puppet-strings/yard/handlers/puppet/defined_type_handler_spec.rb create mode 100644 spec/unit/puppet-strings/yard/handlers/puppet/function_handler_spec.rb create mode 100644 spec/unit/puppet-strings/yard/handlers/ruby/function_handler_spec.rb create mode 100644 spec/unit/puppet-strings/yard/handlers/ruby/provider_handler_spec.rb create mode 100644 spec/unit/puppet-strings/yard/handlers/ruby/type_handler_spec.rb create mode 100644 spec/unit/puppet-strings/yard/parsers/puppet/parser_spec.rb diff --git a/Rakefile b/Rakefile index adb0c64..fa3dcb1 100644 --- a/Rakefile +++ b/Rakefile @@ -6,17 +6,14 @@ require 'puppet-lint/tasks/puppet-lint' require 'puppet-strings/tasks' PuppetLint.configuration.send('disable_80chars') -PuppetLint.configuration.ignore_paths = ["spec/**/*.pp", "pkg/**/*.pp"] +PuppetLint.configuration.ignore_paths = %w(acceptance/**/*.pp spec/**/*.pp pkg/**/*.pp) -desc "Validate manifests, templates, and ruby files" +desc 'Validate Ruby source files and ERB templates.' task :validate do - Dir['manifests/**/*.pp'].each do |manifest| - sh "puppet parser validate --noop #{manifest}" - end Dir['spec/**/*.rb','lib/**/*.rb'].each do |ruby_file| sh "ruby -c #{ruby_file}" unless ruby_file =~ /spec\/fixtures/ end - Dir['templates/**/*.erb'].each do |template| + Dir['lib/puppet-strings/yard/templates/**/*.erb'].each do |template| sh "erb -P -x -T '-' #{template} | ruby -c" end end @@ -32,16 +29,16 @@ task :acceptance do end cli = BeakerHostGenerator::CLI.new([target]) - nodeset_dir = "spec/acceptance/nodesets" + nodeset_dir = 'acceptance/nodesets' nodeset = "#{nodeset_dir}/#{target}.yml" FileUtils.mkdir_p(nodeset_dir) File.open(nodeset, 'w') do |fh| fh.print(cli.execute) end puts nodeset - sh "gem build puppet-strings.gemspec" - sh "puppet module build spec/unit/puppet/examples/test" - sh "BEAKER_set=#{ENV["platform"]} rspec spec/acceptance/*.rb" + sh 'gem build puppet-strings.gemspec' + sh 'puppet module build acceptance/fixtures/modules/test' + sh "BEAKER_set=#{ENV['platform']} rspec acceptance/*.rb" end task(:rubocop) do diff --git a/acceptance/fixtures/modules/test/lib/puppet/functions/4x_function.rb b/acceptance/fixtures/modules/test/lib/puppet/functions/4x_function.rb new file mode 100644 index 0000000..5825496 --- /dev/null +++ b/acceptance/fixtures/modules/test/lib/puppet/functions/4x_function.rb @@ -0,0 +1,5 @@ +# function 4x +# +# This is a function which is used to test puppet strings +Puppet::Functions.create_function(:function4x) do +end diff --git a/acceptance/fixtures/modules/test/lib/puppet/parser/functions/function3x.rb b/acceptance/fixtures/modules/test/lib/puppet/parser/functions/function3x.rb new file mode 100644 index 0000000..e3083ea --- /dev/null +++ b/acceptance/fixtures/modules/test/lib/puppet/parser/functions/function3x.rb @@ -0,0 +1,3 @@ +Puppet::Parser::Functions.newfunction(:function3x, :doc => "This is the +function documentation for `function3x`") do |args| +end diff --git a/acceptance/fixtures/modules/test/manifests/init.pp b/acceptance/fixtures/modules/test/manifests/init.pp new file mode 100644 index 0000000..8ebd451 --- /dev/null +++ b/acceptance/fixtures/modules/test/manifests/init.pp @@ -0,0 +1,27 @@ +# Class: test +# +# This class exists to serve as fixture data for testing the puppet strings face +# +# @example +# class { "test": } +# +# @param package_name The name of the package +# @param service_name The name of the service +class test ( + $package_name = $test::params::package_name, + $service_name = $test::params::service_name, + +) inherits test::params { + + # validate parameters here + + class { 'test::install': } -> + class { 'test::config': } ~> + class { 'test::service': } -> + Class['test'] + + File { + owner => 'user', + path => 'some/file/path', + } +} diff --git a/acceptance/fixtures/modules/test/manifests/triple_nested_classes.pp b/acceptance/fixtures/modules/test/manifests/triple_nested_classes.pp new file mode 100644 index 0000000..6ac95cd --- /dev/null +++ b/acceptance/fixtures/modules/test/manifests/triple_nested_classes.pp @@ -0,0 +1,27 @@ +# Testing tested classes +# docs stuff +# @param nameservers [String] Don't ask me what this does! +# @param default_lease_time [Integer[1024, 8192]] text goes here +# @param max_lease_time does stuff +class outer ( + $dnsdomain, + $nameservers, + $default_lease_time = 3600, + $max_lease_time = 86400 + ) { + # @param options [String[5,7]] gives user choices + # @param multicast [Boolean] foobar + # @param servers yep, that's right + class middle ( + $options = "iburst", + $servers, + $multicast = false + ) { + class inner ( + $choices = "uburst", + $secenekler = "weallburst", + $boxen, + $manyspell = true + ) {} + } +} diff --git a/acceptance/fixtures/modules/test/metadata.json b/acceptance/fixtures/modules/test/metadata.json new file mode 100644 index 0000000..1892d9d --- /dev/null +++ b/acceptance/fixtures/modules/test/metadata.json @@ -0,0 +1,6 @@ +{ + "name": "username-test", + "version": "0.0.1", + "author": "username", + "license": "Apache 2.0" +} diff --git a/acceptance/running_strings_generate.rb b/acceptance/running_strings_generate.rb new file mode 100644 index 0000000..fa13c63 --- /dev/null +++ b/acceptance/running_strings_generate.rb @@ -0,0 +1,28 @@ +require 'spec_helper_acceptance' +require 'json' + +describe 'Generating module documentation using generate action' do + def read_file_on(host, filename) + on(host, "cat #{filename}").stdout + end + + before :all do + modules = JSON.parse(on(master, puppet('module', 'list', '--render-as', 'json')).stdout) + test_module_info = modules['modules_by_path'].values.flatten.find { |mod_info| mod_info =~ /Module test/ } + test_module_path = test_module_info.match(/\(([^)]*)\)/)[1] + + on master, puppet('strings', 'generate', "#{test_module_path}/**/*.{rb,pp}") + end + + it 'should generate documentation for manifests' do + expect(read_file_on(master, '/root/doc/puppet_classes/test.html')).to include('Class: test') + end + + it 'should generate documentation for 3x functions' do + expect(read_file_on(master, '/root/doc/puppet_functions_ruby3x/function3x.html')).to include('This is the function documentation for function3x') + end + + it 'should generate documentation for 4x functions' do + expect(read_file_on(master, '/root/doc/puppet_functions_ruby4x/function4x.html')).to include('This is a function which is used to test puppet strings') + end +end diff --git a/lib/puppet-strings/yard/handlers/puppet/base.rb b/lib/puppet-strings/yard/handlers/puppet/base.rb index 0315b3c..8bcd8d2 100644 --- a/lib/puppet-strings/yard/handlers/puppet/base.rb +++ b/lib/puppet-strings/yard/handlers/puppet/base.rb @@ -23,13 +23,11 @@ class PuppetStrings::Yard::Handlers::Puppet::Base < YARD::Handlers::Base # Assign the types for the parameter statement.parameters.each do |parameter| tag = tags.find { |t| t.name == parameter.name } - unless tag log.warn "Missing @param tag for parameter '#{parameter.name}' near #{statement.file}:#{statement.line}." unless object.docstring.empty? # Add a tag with an empty docstring object.add_tag YARD::Tags::Tag.new(:param, '', [parameter.type || 'Any'], parameter.name) - object.parameters << [parameter.name, parameter.value] next end diff --git a/spec/acceptance/running_strings_yardoc.rb b/spec/acceptance/running_strings_yardoc.rb deleted file mode 100644 index 38b9fd4..0000000 --- a/spec/acceptance/running_strings_yardoc.rb +++ /dev/null @@ -1,28 +0,0 @@ -require 'spec_helper_acceptance' -require 'json' - -describe 'Genearting module documation using yardoc action' do - def read_file_on(host, filename) - on(host, "cat #{filename}").stdout - end - - before :all do - modules = JSON.parse(on(master, puppet("module", "list", "--render-as", "json")).stdout) - test_module_info = modules["modules_by_path"].values.flatten.find { |mod_info| mod_info =~ /Module test/ } - test_module_path = test_module_info.match(/\(([^)]*)\)/)[1] - - on master, puppet("strings", "#{test_module_path}/**/*.{rb,pp}") - end - - it "should generate documentation for manifests" do - expect(read_file_on(master, '/root/doc/test.html')).to include("Class: test") - end - - it "should generate documenation for 3x functions" do - expect(read_file_on(master, '/root/doc/Puppet3xFunctions.html')).to include("This is the function documentation for `function3x`") - end - - it "should generate documenation for 4x functions" do - expect(read_file_on(master, '/root/doc/Puppet4xFunctions.html')).to include("This is a function which is used to test puppet strings") - end -end diff --git a/spec/fixtures/unit/json/output.json b/spec/fixtures/unit/json/output.json new file mode 100644 index 0000000..a6d1197 --- /dev/null +++ b/spec/fixtures/unit/json/output.json @@ -0,0 +1,348 @@ +{ + "puppet_classes": [ + { + "name": "klass", + "file": "(stdin)", + "line": 5, + "inherits": "foo::bar", + "docstring": { + "text": "A simple class.", + "tags": [ + { + "tag_name": "param", + "text": "First param.", + "types": [ + "Integer" + ], + "name": "param1" + }, + { + "tag_name": "param", + "text": "Second param.", + "types": [ + "Any" + ], + "name": "param2" + }, + { + "tag_name": "param", + "text": "Third param.", + "types": [ + "String" + ], + "name": "param3" + } + ] + }, + "defaults": { + "param3": "hi" + }, + "source": "class klass(Integer $param1, $param2, String $param3 = hi) inherits foo::bar {\n}" + } + ], + "defined_types": [ + { + "name": "dt", + "file": "(stdin)", + "line": 12, + "docstring": { + "text": "A simple defined type.", + "tags": [ + { + "tag_name": "param", + "text": "First param.", + "types": [ + "Integer" + ], + "name": "param1" + }, + { + "tag_name": "param", + "text": "Second param.", + "types": [ + "Any" + ], + "name": "param2" + }, + { + "tag_name": "param", + "text": "Third param.", + "types": [ + "String" + ], + "name": "param3" + } + ] + }, + "defaults": { + "param3": "hi" + }, + "source": "define dt(Integer $param1, $param2, String $param3 = hi) {\n}" + } + ], + "resource_types": [ + { + "name": "database", + "file": "(stdin)", + "line": 43, + "docstring": { + "text": "An example database server resource type." + }, + "properties": [ + { + "name": "ensure", + "description": "What state the database should be in.", + "values": [ + "present", + "absent", + "up", + "down" + ], + "aliases": { + "up": "present", + "down": "absent" + }, + "default": "up" + }, + { + "name": "file", + "description": "The database file to use." + }, + { + "name": "log_level", + "description": "The log level to use.", + "values": [ + "debug", + "warn", + "error" + ], + "default": "warn" + } + ], + "parameters": [ + { + "name": "address", + "description": "The database server name.", + "isnamevar": true + }, + { + "name": "encryption_key", + "description": "The encryption key to use." + }, + { + "name": "encrypt", + "description": "Whether or not to encrypt the database.", + "values": [ + "true", + "false", + "yes", + "no" + ], + "default": "false" + } + ], + "features": [ + { + "name": "encryption", + "description": "The provider supports encryption." + } + ] + } + ], + "providers": [ + { + "name": "linux", + "type_name": "database", + "file": "(stdin)", + "line": 33, + "docstring": { + "text": "An example provider on Linux." + }, + "confines": { + "kernel": "Linux", + "osfamily": "RedHat" + }, + "features": [ + "implements_some_feature", + "some_other_feature" + ], + "defaults": { + "kernel": "Linux" + }, + "commands": { + "foo": "/usr/bin/foo" + } + } + ], + "puppet_functions": [ + { + "name": "func", + "file": "(stdin)", + "line": 20, + "type": "puppet", + "signature": "func(Integer $param1, Any $param2, String $param3 = hi)", + "docstring": { + "text": "A simple function.", + "tags": [ + { + "tag_name": "param", + "text": "First param.", + "types": [ + "Integer" + ], + "name": "param1" + }, + { + "tag_name": "param", + "text": "Second param.", + "types": [ + "Any" + ], + "name": "param2" + }, + { + "tag_name": "param", + "text": "Third param.", + "types": [ + "String" + ], + "name": "param3" + }, + { + "tag_name": "return", + "text": "Returns nothing.", + "types": [ + "Undef" + ] + } + ] + }, + "defaults": { + "param3": "hi" + }, + "source": "function func(Integer $param1, $param2, String $param3 = hi) {\n}" + }, + { + "name": "func3x", + "file": "(stdin)", + "line": 1, + "type": "ruby3x", + "signature": "func3x(String $first, Any $second)", + "docstring": { + "text": "An example 3.x function.", + "tags": [ + { + "tag_name": "param", + "text": "The first parameter.", + "types": [ + "String" + ], + "name": "first" + }, + { + "tag_name": "param", + "text": "The second parameter.", + "types": [ + "Any" + ], + "name": "second" + }, + { + "tag_name": "return", + "text": "Returns nothing.", + "types": [ + "Undef" + ] + } + ] + }, + "source": "Puppet::Parser::Functions.newfunction(:func3x, doc: <<-DOC\nAn example 3.x function.\n@param [String] first The first parameter.\n@param second The second parameter.\n@return [Undef] Returns nothing.\nDOC\n) do |*args|\nend" + }, + { + "name": "func4x", + "file": "(stdin)", + "line": 11, + "type": "ruby4x", + "docstring": { + "text": "An example 4.x function.", + "tags": [ + { + "tag_name": "overload", + "signature": "func4x(Integer $param1, Any $param2, Optional[Array[String]] $param3)", + "docstring": { + "text": "The first overload.", + "tags": [ + { + "tag_name": "param", + "text": "The first parameter.", + "types": [ + "Integer" + ], + "name": "param1" + }, + { + "tag_name": "param", + "text": "The second parameter.", + "types": [ + "Any" + ], + "name": "param2" + }, + { + "tag_name": "param", + "text": "The third parameter.", + "types": [ + "Optional[Array[String]]" + ], + "name": "param3" + }, + { + "tag_name": "return", + "text": "Returns nothing.", + "types": [ + "Undef" + ] + } + ] + }, + "name": "func4x" + }, + { + "tag_name": "overload", + "signature": "func4x(Boolean $param, Callable &$block)", + "docstring": { + "text": "The second overload.", + "tags": [ + { + "tag_name": "param", + "text": "The first parameter.", + "types": [ + "Boolean" + ], + "name": "param" + }, + { + "tag_name": "param", + "text": "The block parameter.", + "types": [ + "Callable" + ], + "name": "&block" + }, + { + "tag_name": "return", + "text": "Returns a string.", + "types": [ + "String" + ] + } + ] + }, + "name": "func4x" + } + ] + }, + "source": "Puppet::Functions.create_function(:func4x) do\n # The first overload.\n # @param param1 The first parameter.\n # @param param2 The second parameter.\n # @param param3 The third parameter.\n # @return [Undef] Returns nothing.\n dispatch :foo do\n param 'Integer', :param1\n param 'Any', :param2\n optional_param 'Array[String]', :param3\n end\n\n # The second overload.\n # @param param The first parameter.\n # @param block The block parameter.\n # @return [String] Returns a string.\n dispatch :other do\n param 'Boolean', :param\n block_param\n end\nend" + } + ] +} diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index bf10b10..ac86301 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,13 +1,17 @@ -dir = File.expand_path(File.dirname(__FILE__)) -$LOAD_PATH.unshift File.join(dir, 'lib') - require 'mocha' -require 'puppet' require 'rspec' - require 'puppet-strings' +require 'puppet-strings/yard' + +# Explicitly set up YARD once +PuppetStrings::Yard.setup! RSpec.configure do |config| - config.mock_with :mocha + config.mock_with :mocha + + config.before(:each) do + # Always clear the YARD registry before each example + YARD::Registry.clear + end end diff --git a/spec/spec_helper_acceptance.rb b/spec/spec_helper_acceptance.rb index 5a69594..a948d98 100644 --- a/spec/spec_helper_acceptance.rb +++ b/spec/spec_helper_acceptance.rb @@ -12,12 +12,11 @@ RSpec.configure do |c| # Configure all nodes in nodeset c.before :suite do - - hosts.each do |host| + hosts.each do |host| scp_to(host, Dir.glob('puppet-strings*.gem').first, 'puppet-strings.gem') on host, 'gem install puppet-strings.gem' - scp_to(host, Dir.glob('spec/unit/puppet/examples/test/pkg/username-test*.gz').first, 'test.tar.gz') + scp_to(host, Dir.glob('acceptance/fixtures/modules/test/pkg/username-test*.gz').first, 'test.tar.gz') on host, puppet('module', 'install', 'test.tar.gz') on host, 'gem install yard' diff --git a/spec/unit/puppet-strings/json_spec.rb b/spec/unit/puppet-strings/json_spec.rb new file mode 100644 index 0000000..4174f92 --- /dev/null +++ b/spec/unit/puppet-strings/json_spec.rb @@ -0,0 +1,132 @@ +require 'spec_helper' +require 'puppet-strings/json' +require 'tempfile' + +describe PuppetStrings::Json do + before :each do + # Populate the YARD registry with both Puppet and Ruby source + YARD::Parser::SourceParser.parse_string(<<-SOURCE, :puppet) +# A simple class. +# @param param1 First param. +# @param param2 Second param. +# @param param3 Third param. +class klass(Integer $param1, $param2, String $param3 = hi) inherits foo::bar { +} + +# A simple defined type. +# @param param1 First param. +# @param param2 Second param. +# @param param3 Third param. +define dt(Integer $param1, $param2, String $param3 = hi) { +} + +# A simple function. +# @param param1 First param. +# @param param2 Second param. +# @param param3 Third param. +# @return [Undef] Returns nothing. +function func(Integer $param1, $param2, String $param3 = hi) { +} +SOURCE + + YARD::Parser::SourceParser.parse_string(<<-SOURCE, :ruby) +Puppet::Parser::Functions.newfunction(:func3x, doc: <<-DOC +An example 3.x function. +@param [String] first The first parameter. +@param second The second parameter. +@return [Undef] Returns nothing. +DOC +) do |*args| +end + +# An example 4.x function. +Puppet::Functions.create_function(:func4x) do + # The first overload. + # @param param1 The first parameter. + # @param param2 The second parameter. + # @param param3 The third parameter. + # @return [Undef] Returns nothing. + dispatch :foo do + param 'Integer', :param1 + param 'Any', :param2 + optional_param 'Array[String]', :param3 + end + + # The second overload. + # @param param The first parameter. + # @param block The block parameter. + # @return [String] Returns a string. + dispatch :other do + param 'Boolean', :param + block_param + end +end + +Puppet::Type.type(:database).provide :linux do + desc 'An example provider on Linux.' + confine kernel: 'Linux' + confine osfamily: 'RedHat' + defaultfor kernel: 'Linux' + has_feature :implements_some_feature + has_feature :some_other_feature + commands foo: /usr/bin/foo +end + +Puppet::Type.newtype(:database) do + desc 'An example database server resource type.' + feature :encryption, 'The provider supports encryption.', methods: [:encrypt] + ensurable do + desc 'What state the database should be in.' + defaultvalues + aliasvalue(:up, :present) + aliasvalue(:down, :absent) + defaultto :up + end + + newparam(:address) do + isnamevar + desc 'The database server name.' + end + + newparam(:encryption_key, required_features: :encryption) do + desc 'The encryption key to use.' + end + + newparam(:encrypt, :parent => Puppet::Parameter::Boolean) do + desc 'Whether or not to encrypt the database.' + defaultto false + end + + newproperty(:file) do + desc 'The database file to use.' + end + + newproperty(:log_level) do + desc 'The log level to use.' + newvalue(:debug) + newvalue(:warn) + newvalue(:error) + defaultto 'warn' + end +end +SOURCE + end + + let(:baseline_path) { File.join(File.dirname(__FILE__), '../../fixtures/unit/json/output.json') } + let(:baseline) { File.read(baseline_path) } + + describe 'rendering JSON to a file' do + it 'should output the expected JSON content' do + Tempfile.open('json') do |file| + PuppetStrings::Json.render(file.path) + expect(File.read(file.path)).to eq(baseline) + end + end + end + + describe 'rendering JSON to stdout' do + it 'should output the expected JSON content' do + expect{ PuppetStrings::Json.render(nil) }.to output(baseline).to_stdout + end + end +end diff --git a/spec/unit/puppet-strings/yard/handlers/puppet/class_handler_spec.rb b/spec/unit/puppet-strings/yard/handlers/puppet/class_handler_spec.rb new file mode 100644 index 0000000..cc5b7c4 --- /dev/null +++ b/spec/unit/puppet-strings/yard/handlers/puppet/class_handler_spec.rb @@ -0,0 +1,155 @@ +require 'spec_helper' +require 'puppet-strings/yard' + +describe PuppetStrings::Yard::Handlers::Puppet::ClassHandler do + subject { + YARD::Parser::SourceParser.parse_string(source, :puppet) + YARD::Registry.all(:puppet_class) + } + + describe 'parsing source without a class definition' do + let(:source) { 'notice hi' } + + it 'no classes should be in the registry' do + expect(subject.empty?).to eq(true) + end + end + + describe 'parsing source with a syntax error' do + let(:source) { 'class foo{' } + + it 'should log an error' do + expect{ subject }.to output(/\[error\]: Failed to parse \(stdin\): Syntax error at end of file/).to_stdout_from_any_process + expect(subject.empty?).to eq(true) + end + end + + describe 'parsing a class with a missing docstring' do + let(:source) { 'class foo{}' } + + it 'should log a warning' do + expect{ subject }.to output(/\[warn\]: Missing documentation for Puppet class 'foo' at \(stdin\):1\./).to_stdout_from_any_process + end + end + + describe 'parsing a class with a docstring' do + let(:source) { <<-SOURCE +# A simple foo class. +# @param param1 First param. +# @param param2 Second param. +# @param param3 Third param. +class foo(Integer $param1, $param2, String $param3 = hi) inherits foo::bar { + file { '/tmp/foo': + ensure => present + } +} +SOURCE + } + + it 'should register a class object' do + expect(subject.size).to eq(1) + object = subject.first + expect(object).to be_a(PuppetStrings::Yard::CodeObjects::Class) + expect(object.namespace).to eq(PuppetStrings::Yard::CodeObjects::Classes.instance) + expect(object.name).to eq(:foo) + expect(object.statement).not_to eq(nil) + expect(object.parameters).to eq([['param1', nil], ['param2', nil], ['param3', 'hi']]) + expect(object.docstring).to eq('A simple foo class.') + expect(object.docstring.tags.size).to eq(4) + tags = object.docstring.tags(:param) + expect(tags.size).to eq(3) + expect(tags[0].name).to eq('param1') + expect(tags[0].text).to eq('First param.') + expect(tags[0].types).to eq(['Integer']) + expect(tags[1].name).to eq('param2') + expect(tags[1].text).to eq('Second param.') + expect(tags[1].types).to eq(['Any']) + expect(tags[2].name).to eq('param3') + expect(tags[2].text).to eq('Third param.') + expect(tags[2].types).to eq(['String']) + tags = object.docstring.tags(:api) + expect(tags.size).to eq(1) + expect(tags[0].text).to eq('public') + end + end + + describe 'parsing a class with a missing parameter' do + let(:source) { <<-SOURCE +# A simple foo class. +# @param param1 First param. +# @param param2 Second param. +# @param param3 Third param. +# @param param4 missing! +class foo(Integer $param1, $param2, String $param3 = hi) inherits foo::bar { + file { '/tmp/foo': + ensure => present + } +} +SOURCE + } + + it 'should output a warning' do + expect{ subject }.to output(/\[warn\]: The @param tag for parameter 'param4' has no matching parameter at \(stdin\):6\./).to_stdout_from_any_process + end + end + + describe 'parsing a class with a missing @param tag' do + let(:source) { <<-SOURCE +# A simple foo class. +# @param param1 First param. +# @param param2 Second param. +class foo(Integer $param1, $param2, String $param3 = hi) inherits foo::bar { + file { '/tmp/foo': + ensure => present + } +} +SOURCE + } + + it 'should output a warning' do + expect{ subject }.to output(/\[warn\]: Missing @param tag for parameter 'param3' near \(stdin\):4\./).to_stdout_from_any_process + end + end + + describe 'parsing a class with a typed parameter that also has a @param tag type' do + let(:source) { <<-SOURCE +# A simple foo class. +# @param [Boolean] param1 First param. +# @param param2 Second param. +# @param param3 Third param. +class foo(Integer $param1, $param2, String $param3 = hi) inherits foo::bar { + file { '/tmp/foo': + ensure => present + } +} +SOURCE + } + + it 'should output a warning' do + expect{ subject }.to output(/\[warn\]: The @param tag for parameter 'param1' should not contain a type specification near \(stdin\):5: ignoring in favor of parameter type information\./).to_stdout_from_any_process + end + end + + describe 'parsing a class with a untyped parameter that also has a @param tag type' do + let(:source) { <<-SOURCE +# A simple foo class. +# @param param1 First param. +# @param [Boolean] param2 Second param. +# @param param3 Third param. +class foo(Integer $param1, $param2, String $param3 = hi) inherits foo::bar { + file { '/tmp/foo': + ensure => present + } +} +SOURCE + } + + it 'should respect the type that was documented' do + expect{ subject }.to output('').to_stdout_from_any_process + expect(subject.size).to eq(1) + tags = subject.first.tags(:param) + expect(tags.size).to eq(3) + expect(tags[1].types).to eq(['Boolean']) + end + end +end diff --git a/spec/unit/puppet-strings/yard/handlers/puppet/defined_type_handler_spec.rb b/spec/unit/puppet-strings/yard/handlers/puppet/defined_type_handler_spec.rb new file mode 100644 index 0000000..633f8f0 --- /dev/null +++ b/spec/unit/puppet-strings/yard/handlers/puppet/defined_type_handler_spec.rb @@ -0,0 +1,155 @@ +require 'spec_helper' +require 'puppet-strings/yard' + +describe PuppetStrings::Yard::Handlers::Puppet::DefinedTypeHandler do + subject { + YARD::Parser::SourceParser.parse_string(source, :puppet) + YARD::Registry.all(:puppet_defined_type) + } + + describe 'parsing source without a defined type definition' do + let(:source) { 'notice hi' } + + it 'no defined types should be in the registry' do + expect(subject.empty?).to eq(true) + end + end + + describe 'parsing source with a syntax error' do + let(:source) { 'define foo{' } + + it 'should log an error' do + expect{ subject }.to output(/\[error\]: Failed to parse \(stdin\): Syntax error at end of file/).to_stdout_from_any_process + expect(subject.empty?).to eq(true) + end + end + + describe 'parsing a defined type with a missing docstring' do + let(:source) { 'define foo{}' } + + it 'should log a warning' do + expect{ subject }.to output(/\[warn\]: Missing documentation for Puppet defined type 'foo' at \(stdin\):1\./).to_stdout_from_any_process + end + end + + describe 'parsing a defined type with a docstring' do + let(:source) { <<-SOURCE +# A simple foo defined type. +# @param param1 First param. +# @param param2 Second param. +# @param param3 Third param. +define foo(Integer $param1, $param2, String $param3 = hi) { + file { '/tmp/foo': + ensure => present + } +} +SOURCE + } + + it 'should register a defined type object' do + expect(subject.size).to eq(1) + object = subject.first + expect(object).to be_a(PuppetStrings::Yard::CodeObjects::DefinedType) + expect(object.namespace).to eq(PuppetStrings::Yard::CodeObjects::DefinedTypes.instance) + expect(object.name).to eq(:foo) + expect(object.statement).not_to eq(nil) + expect(object.parameters).to eq([['param1', nil], ['param2', nil], ['param3', 'hi']]) + expect(object.docstring).to eq('A simple foo defined type.') + expect(object.docstring.tags.size).to eq(4) + tags = object.docstring.tags(:param) + expect(tags.size).to eq(3) + expect(tags[0].name).to eq('param1') + expect(tags[0].text).to eq('First param.') + expect(tags[0].types).to eq(['Integer']) + expect(tags[1].name).to eq('param2') + expect(tags[1].text).to eq('Second param.') + expect(tags[1].types).to eq(['Any']) + expect(tags[2].name).to eq('param3') + expect(tags[2].text).to eq('Third param.') + expect(tags[2].types).to eq(['String']) + tags = object.docstring.tags(:api) + expect(tags.size).to eq(1) + expect(tags[0].text).to eq('public') + end + end + + describe 'parsing a defined type with a missing parameter' do + let(:source) { <<-SOURCE +# A simple foo defined type. +# @param param1 First param. +# @param param2 Second param. +# @param param3 Third param. +# @param param4 missing! +define foo(Integer $param1, $param2, String $param3 = hi) { + file { '/tmp/foo': + ensure => present + } +} +SOURCE + } + + it 'should output a warning' do + expect{ subject }.to output(/\[warn\]: The @param tag for parameter 'param4' has no matching parameter at \(stdin\):6\./).to_stdout_from_any_process + end + end + + describe 'parsing a defined type with a missing @param tag' do + let(:source) { <<-SOURCE +# A simple foo defined type. +# @param param1 First param. +# @param param2 Second param. +define foo(Integer $param1, $param2, String $param3 = hi) { + file { '/tmp/foo': + ensure => present + } +} +SOURCE + } + + it 'should output a warning' do + expect{ subject }.to output(/\[warn\]: Missing @param tag for parameter 'param3' near \(stdin\):4\./).to_stdout_from_any_process + end + end + + describe 'parsing a defined type with a typed parameter that also has a @param tag type' do + let(:source) { <<-SOURCE +# A simple foo defined type. +# @param [Boolean] param1 First param. +# @param param2 Second param. +# @param param3 Third param. +define foo(Integer $param1, $param2, String $param3 = hi) { + file { '/tmp/foo': + ensure => present + } +} +SOURCE + } + + it 'should output a warning' do + expect{ subject }.to output(/\[warn\]: The @param tag for parameter 'param1' should not contain a type specification near \(stdin\):5: ignoring in favor of parameter type information\./).to_stdout_from_any_process + end + end + + describe 'parsing a defined type with a untyped parameter that also has a @param tag type' do + let(:source) { <<-SOURCE +# A simple foo defined type. +# @param param1 First param. +# @param [Boolean] param2 Second param. +# @param param3 Third param. +define foo(Integer $param1, $param2, String $param3 = hi) { + file { '/tmp/foo': + ensure => present + } +} +SOURCE + } + + it 'should respect the type that was documented' do + expect{ subject }.to output('').to_stdout_from_any_process + expect(subject.size).to eq(1) + tags = subject.first.tags(:param) + expect(tags.size).to eq(3) + expect(tags[1].types).to eq(['Boolean']) + end + end +end diff --git a/spec/unit/puppet-strings/yard/handlers/puppet/function_handler_spec.rb b/spec/unit/puppet-strings/yard/handlers/puppet/function_handler_spec.rb new file mode 100644 index 0000000..81ae0be --- /dev/null +++ b/spec/unit/puppet-strings/yard/handlers/puppet/function_handler_spec.rb @@ -0,0 +1,168 @@ +require 'spec_helper' +require 'puppet-strings/yard' + +describe PuppetStrings::Yard::Handlers::Puppet::FunctionHandler do + subject { + YARD::Parser::SourceParser.parse_string(source, :puppet) + YARD::Registry.all(:puppet_function) + } + + describe 'parsing source without a function definition' do + let(:source) { 'notice hi' } + + it 'no functions should be in the registry' do + expect(subject.empty?).to eq(true) + end + end + + describe 'parsing source with a syntax error' do + let(:source) { 'function foo{' } + + it 'should log an error' do + expect{ subject }.to output(/\[error\]: Failed to parse \(stdin\): Syntax error at end of file/).to_stdout_from_any_process + expect(subject.empty?).to eq(true) + end + end + + describe 'parsing a function with a missing docstring' do + let(:source) { 'function foo{}' } + + it 'should log a warning' do + expect{ subject }.to output(/\[warn\]: Missing documentation for Puppet function 'foo' at \(stdin\):1\./).to_stdout_from_any_process + end + end + + describe 'parsing a function with a docstring' do + let(:source) { <<-SOURCE +# A simple foo function. +# @param param1 First param. +# @param param2 Second param. +# @param param3 Third param. +# @return [Undef] Returns nothing. +function foo(Integer $param1, $param2, String $param3 = hi) { + notice 'hello world' + undef +} +SOURCE + } + + it 'should register a function object' do + expect(subject.size).to eq(1) + object = subject.first + expect(object).to be_a(PuppetStrings::Yard::CodeObjects::Function) + expect(object.namespace).to eq(PuppetStrings::Yard::CodeObjects::Functions.instance(PuppetStrings::Yard::CodeObjects::Function::PUPPET)) + expect(object.name).to eq(:foo) + expect(object.signature).to eq('foo(Integer $param1, Any $param2, String $param3 = hi)') + expect(object.parameters).to eq([['param1', nil], ['param2', nil], ['param3', 'hi']]) + expect(object.docstring).to eq('A simple foo function.') + expect(object.docstring.tags.size).to eq(5) + tags = object.docstring.tags(:param) + expect(tags.size).to eq(3) + expect(tags[0].name).to eq('param1') + expect(tags[0].text).to eq('First param.') + expect(tags[0].types).to eq(['Integer']) + expect(tags[1].name).to eq('param2') + expect(tags[1].text).to eq('Second param.') + expect(tags[1].types).to eq(['Any']) + expect(tags[2].name).to eq('param3') + expect(tags[2].text).to eq('Third param.') + expect(tags[2].types).to eq(['String']) + tags = object.docstring.tags(:api) + expect(tags.size).to eq(1) + expect(tags[0].text).to eq('public') + end + end + + describe 'parsing a function with a missing parameter' do + let(:source) { <<-SOURCE +# A simple foo function. +# @param param1 First param. +# @param param2 Second param. +# @param param3 Third param. +# @param param4 missing! +# @return [Undef] Returns nothing. +function foo(Integer $param1, $param2, String $param3 = hi) { + notice 'hello world' +} +SOURCE + } + + it 'should output a warning' do + expect{ subject }.to output(/\[warn\]: The @param tag for parameter 'param4' has no matching parameter at \(stdin\):7\./).to_stdout_from_any_process + end + end + + describe 'parsing a function with a missing @param tag' do + let(:source) { <<-SOURCE +# A simple foo function. +# @param param1 First param. +# @param param2 Second param. +# @return [Undef] Returns nothing. +function foo(Integer $param1, $param2, String $param3 = hi) { + notice 'hello world' +} +SOURCE + } + + it 'should output a warning' do + expect{ subject }.to output(/\[warn\]: Missing @param tag for parameter 'param3' near \(stdin\):5\./).to_stdout_from_any_process + end + end + + describe 'parsing a function with a typed parameter that also has a @param tag type' do + let(:source) { <<-SOURCE +# A simple foo function. +# @param [Boolean] param1 First param. +# @param param2 Second param. +# @param param3 Third param. +# @return [Undef] Returns nothing. +function foo(Integer $param1, $param2, String $param3 = hi) { + notice 'hello world' +} +SOURCE + } + + it 'should output a warning' do + expect{ subject }.to output(/\[warn\]: The @param tag for parameter 'param1' should not contain a type specification near \(stdin\):6: ignoring in favor of parameter type information\./).to_stdout_from_any_process + end + end + + describe 'parsing a function with a untyped parameter that also has a @param tag type' do + let(:source) { <<-SOURCE +# A simple foo function. +# @param param1 First param. +# @param [Boolean] param2 Second param. +# @param param3 Third param. +# @return [Undef] Returns nothing. +function foo(Integer $param1, $param2, String $param3 = hi) { + notice 'hello world' +} +SOURCE + } + + it 'should respect the type that was documented' do + expect{ subject }.to output('').to_stdout_from_any_process + expect(subject.size).to eq(1) + tags = subject.first.tags(:param) + expect(tags.size).to eq(3) + expect(tags[1].types).to eq(['Boolean']) + end + end + + describe 'parsing a function with a missing @return tag' do + let(:source) { <<-SOURCE +# A simple foo function. +# @param param1 First param. +# @param param2 Second param. +# @param param3 Third param. +function foo(Integer $param1, $param2, String $param3 = hi) { + notice 'hello world' +} +SOURCE + } + + it 'should output a warning' do + expect{ subject }.to output(/\[warn\]: Missing @return tag near \(stdin\):5\./).to_stdout_from_any_process + end + end +end diff --git a/spec/unit/puppet-strings/yard/handlers/ruby/function_handler_spec.rb b/spec/unit/puppet-strings/yard/handlers/ruby/function_handler_spec.rb new file mode 100644 index 0000000..d80e5a0 --- /dev/null +++ b/spec/unit/puppet-strings/yard/handlers/ruby/function_handler_spec.rb @@ -0,0 +1,613 @@ +require 'spec_helper' +require 'puppet-strings/yard' + +describe PuppetStrings::Yard::Handlers::Ruby::FunctionHandler do + subject { + YARD::Parser::SourceParser.parse_string(source, :ruby) + YARD::Registry.all(:puppet_function) + } + + describe 'parsing source without a function definition' do + let(:source) { 'puts "hi"' } + + it 'no functions should be in the registry' do + expect(subject.empty?).to eq(true) + end + end + + describe 'parsing 3.x API functions' do + describe 'parsing a function with a missing docstring' do + let(:source) { <<-SOURCE +Puppet::Parser::Functions.newfunction(:foo) do |*args| +end +SOURCE + } + + it 'should log a warning' do + expect{ subject }.to output(/\[warn\]: Missing documentation for Puppet function 'foo' at \(stdin\):1\./).to_stdout_from_any_process + end + end + + describe 'parsing a function with a doc parameter' do + let(:source) { <<-SOURCE +Puppet::Parser::Functions.newfunction(:foo, doc: <<-DOC +An example 3.x function. +@param [String] first The first parameter. +@param second The second parameter. +@return [Undef] Returns nothing. +DOC +) do |*args| +end +SOURCE + } + + it 'should register a function object' do + expect(subject.size).to eq(1) + object = subject.first + expect(object).to be_a(PuppetStrings::Yard::CodeObjects::Function) + expect(object.namespace).to eq(PuppetStrings::Yard::CodeObjects::Functions.instance(PuppetStrings::Yard::CodeObjects::Function::RUBY_3X)) + expect(object.name).to eq(:foo) + expect(object.signature).to eq('foo(String $first, Any $second)') + expect(object.parameters).to eq([['first', nil], ['second', nil]]) + expect(object.docstring).to eq('An example 3.x function.') + expect(object.docstring.tags.size).to eq(4) + tags = object.docstring.tags(:param) + expect(tags.size).to eq(2) + expect(tags[0].name).to eq('first') + expect(tags[0].text).to eq('The first parameter.') + expect(tags[0].types).to eq(['String']) + expect(tags[1].name).to eq('second') + expect(tags[1].text).to eq('The second parameter.') + expect(tags[1].types).to eq(['Any']) + tags = object.docstring.tags(:return) + expect(tags.size).to eq(1) + expect(tags[0].name).to be_nil + expect(tags[0].text).to eq('Returns nothing.') + expect(tags[0].types).to eq(['Undef']) + tags = object.docstring.tags(:api) + expect(tags.size).to eq(1) + expect(tags[0].text).to eq('public') + end + end + + describe 'parsing a function with a missing @return tag' do + let(:source) { <<-SOURCE +Puppet::Parser::Functions.newfunction(:foo, doc: <<-DOC) do |*args| +An example 3.x function. +@param [String] first The first parameter. +@param second The second parameter. +DOC +end +SOURCE + } + + it 'should log a warning' do + expect{ subject }.to output(/\[warn\]: Missing @return tag near \(stdin\):1/).to_stdout_from_any_process + end + end + end + + describe 'parsing 4.x API functions' do + describe 'parsing a function with a missing docstring' do + let(:source) { <<-SOURCE +Puppet::Functions.create_function(:foo) do +end +SOURCE + } + + it 'should log a warning' do + expect{ subject }.to output(/\[warn\]: Missing documentation for Puppet function 'foo' at \(stdin\):1\./).to_stdout_from_any_process + end + end + + describe 'parsing a function with a simple docstring' do + let(:source) { <<-SOURCE +# An example 4.x function. +Puppet::Functions.create_function(:foo) do +end +SOURCE + } + + it 'should register a function object' do + expect(subject.size).to eq(1) + object = subject.first + expect(object).to be_a(PuppetStrings::Yard::CodeObjects::Function) + expect(object.namespace).to eq(PuppetStrings::Yard::CodeObjects::Functions.instance(PuppetStrings::Yard::CodeObjects::Function::RUBY_4X)) + expect(object.name).to eq(:foo) + expect(object.signature).to eq('foo()') + expect(object.parameters).to eq([]) + expect(object.docstring).to eq('An example 4.x function.') + expect(object.docstring.tags.size).to eq(1) + tags = object.docstring.tags(:api) + expect(tags.size).to eq(1) + expect(tags[0].text).to eq('public') + end + end + + describe 'parsing a function without any dispatches' do + let(:source) { <<-SOURCE +# An example 4.x function. +Puppet::Functions.create_function(:foo) do + # @param [Integer] param1 The first parameter. + # @param param2 The second parameter. + # @param [String] param3 The third parameter. + # @return [Undef] Returns nothing. + def foo(param1, param2, param3 = nil) + end +end +SOURCE + } + + it 'should register a function object' do + expect(subject.size).to eq(1) + object = subject.first + expect(object).to be_a(PuppetStrings::Yard::CodeObjects::Function) + expect(object.namespace).to eq(PuppetStrings::Yard::CodeObjects::Functions.instance(PuppetStrings::Yard::CodeObjects::Function::RUBY_4X)) + expect(object.name).to eq(:foo) + expect(object.signature).to eq('foo(Integer $param1, Any $param2, Optional[String] $param3 = undef)') + expect(object.parameters).to eq([['param1', nil], ['param2', nil], ['param3', 'undef']]) + expect(object.docstring).to eq('An example 4.x function.') + expect(object.docstring.tags.size).to eq(5) + tags = object.docstring.tags(:param) + expect(tags.size).to eq(3) + expect(tags[0].name).to eq('param1') + expect(tags[0].text).to eq('The first parameter.') + expect(tags[0].types).to eq(['Integer']) + expect(tags[1].name).to eq('param2') + expect(tags[1].text).to eq('The second parameter.') + expect(tags[1].types).to eq(['Any']) + expect(tags[2].name).to eq('param3') + expect(tags[2].text).to eq('The third parameter.') + expect(tags[2].types).to eq(['Optional[String]']) + tags = object.docstring.tags(:return) + expect(tags.size).to eq(1) + expect(tags[0].name).to be_nil + expect(tags[0].text).to eq('Returns nothing.') + expect(tags[0].types).to eq(['Undef']) + tags = object.docstring.tags(:api) + expect(tags.size).to eq(1) + expect(tags[0].text).to eq('public') + end + end + + describe 'parsing a function with a single dispatch' do + let(:source) { <<-SOURCE +# An example 4.x function. +Puppet::Functions.create_function(:foo) do + # @param param1 The first parameter. + # @param param2 The second parameter. + # @param param3 The third parameter. + # @return [Undef] Returns nothing. + dispatch :foo do + param 'Integer', :param1 + param 'Any', :param2 + optional_param 'Array[String]', :param3 + end + + def foo(param1, param2, param3 = nil) + end +end +SOURCE + } + + it 'should register a function object without any overload tags' do + expect(subject.size).to eq(1) + object = subject.first + expect(object).to be_a(PuppetStrings::Yard::CodeObjects::Function) + expect(object.namespace).to eq(PuppetStrings::Yard::CodeObjects::Functions.instance(PuppetStrings::Yard::CodeObjects::Function::RUBY_4X)) + expect(object.name).to eq(:foo) + expect(object.signature).to eq('foo(Integer $param1, Any $param2, Optional[Array[String]] $param3)') + expect(object.parameters).to eq([['param1', nil], ['param2', nil], ['param3', nil]]) + expect(object.docstring).to eq('An example 4.x function.') + expect(object.docstring.tags(:overload).empty?).to be_truthy + expect(object.docstring.tags.size).to eq(5) + tags = object.docstring.tags(:param) + expect(tags.size).to eq(3) + expect(tags[0].name).to eq('param1') + expect(tags[0].text).to eq('The first parameter.') + expect(tags[0].types).to eq(['Integer']) + expect(tags[1].name).to eq('param2') + expect(tags[1].text).to eq('The second parameter.') + expect(tags[1].types).to eq(['Any']) + expect(tags[2].name).to eq('param3') + expect(tags[2].text).to eq('The third parameter.') + expect(tags[2].types).to eq(['Optional[Array[String]]']) + tags = object.docstring.tags(:return) + expect(tags.size).to eq(1) + expect(tags[0].name).to be_nil + expect(tags[0].text).to eq('Returns nothing.') + expect(tags[0].types).to eq(['Undef']) + tags = object.docstring.tags(:api) + expect(tags.size).to eq(1) + expect(tags[0].text).to eq('public') + end + end + + describe 'parsing a function with various dispatch parameters.' do + let(:source) { <<-SOURCE +# An example 4.x function. +Puppet::Functions.create_function(:foo) do + # @param param1 The first parameter. + # @param param2 The second parameter. + # @param param3 The third parameter. + # @param param4 The fourth parameter. + # @return [Undef] Returns nothing. + dispatch :foo do + param 'String', :param1 + required_param 'Integer', :param2 + optional_param 'Array', :param3 + repeated_param 'String', :param4 + end +end +SOURCE + } + + it 'should register a function object with the expected parameters' do + expect(subject.size).to eq(1) + object = subject.first + expect(object).to be_a(PuppetStrings::Yard::CodeObjects::Function) + expect(object.namespace).to eq(PuppetStrings::Yard::CodeObjects::Functions.instance(PuppetStrings::Yard::CodeObjects::Function::RUBY_4X)) + expect(object.name).to eq(:foo) + expect(object.signature).to eq('foo(String $param1, Integer $param2, Optional[Array] $param3, String *$param4)') + expect(object.parameters).to eq([['param1', nil], ['param2', nil], ['param3', nil], ['*param4', nil]]) + expect(object.docstring).to eq('An example 4.x function.') + expect(object.docstring.tags(:overload).empty?).to be_truthy + expect(object.docstring.tags.size).to eq(6) + tags = object.docstring.tags(:param) + expect(tags.size).to eq(4) + expect(tags[0].name).to eq('param1') + expect(tags[0].text).to eq('The first parameter.') + expect(tags[0].types).to eq(['String']) + expect(tags[1].name).to eq('param2') + expect(tags[1].text).to eq('The second parameter.') + expect(tags[1].types).to eq(['Integer']) + expect(tags[2].name).to eq('param3') + expect(tags[2].text).to eq('The third parameter.') + expect(tags[2].types).to eq(['Optional[Array]']) + expect(tags[3].name).to eq('*param4') + expect(tags[3].text).to eq('The fourth parameter.') + expect(tags[3].types).to eq(['String']) + tags = object.docstring.tags(:return) + expect(tags.size).to eq(1) + expect(tags[0].name).to be_nil + expect(tags[0].text).to eq('Returns nothing.') + expect(tags[0].types).to eq(['Undef']) + tags = object.docstring.tags(:api) + expect(tags.size).to eq(1) + expect(tags[0].text).to eq('public') + end + end + + describe 'parsing a function with an optional repeated param.' do + let(:source) { <<-SOURCE +# An example 4.x function. +Puppet::Functions.create_function(:foo) do + # @param param The first parameter. + # @return [Undef] Returns nothing. + dispatch :foo do + optional_repeated_param 'String', :param + end +end +SOURCE + } + + it 'should register a function object with the expected parameters' do + expect(subject.size).to eq(1) + object = subject.first + expect(object).to be_a(PuppetStrings::Yard::CodeObjects::Function) + expect(object.namespace).to eq(PuppetStrings::Yard::CodeObjects::Functions.instance(PuppetStrings::Yard::CodeObjects::Function::RUBY_4X)) + expect(object.name).to eq(:foo) + expect(object.signature).to eq('foo(Optional[String] *$param)') + expect(object.parameters).to eq([['*param', nil]]) + expect(object.docstring).to eq('An example 4.x function.') + expect(object.docstring.tags(:overload).empty?).to be_truthy + expect(object.docstring.tags.size).to eq(3) + tags = object.docstring.tags(:param) + expect(tags.size).to eq(1) + expect(tags[0].name).to eq('*param') + expect(tags[0].text).to eq('The first parameter.') + expect(tags[0].types).to eq(['Optional[String]']) + tags = object.docstring.tags(:return) + expect(tags.size).to eq(1) + expect(tags[0].name).to be_nil + expect(tags[0].text).to eq('Returns nothing.') + expect(tags[0].types).to eq(['Undef']) + tags = object.docstring.tags(:api) + expect(tags.size).to eq(1) + expect(tags[0].text).to eq('public') + end + end + + describe 'parsing a function with a block param with one parameter' do + let(:source) { <<-SOURCE +# An example 4.x function. +Puppet::Functions.create_function(:foo) do + # @param a_block The block parameter. + # @return [Undef] Returns nothing. + dispatch :foo do + block_param :a_block + end +end + SOURCE + } + + it 'should register a function object with the expected parameters' do + expect(subject.size).to eq(1) + object = subject.first + expect(object).to be_a(PuppetStrings::Yard::CodeObjects::Function) + expect(object.namespace).to eq(PuppetStrings::Yard::CodeObjects::Functions.instance(PuppetStrings::Yard::CodeObjects::Function::RUBY_4X)) + expect(object.name).to eq(:foo) + expect(object.signature).to eq('foo(Callable &$a_block)') + expect(object.parameters).to eq([['&a_block', nil]]) + expect(object.docstring).to eq('An example 4.x function.') + expect(object.docstring.tags(:overload).empty?).to be_truthy + expect(object.docstring.tags.size).to eq(3) + tags = object.docstring.tags(:param) + expect(tags.size).to eq(1) + expect(tags[0].name).to eq('&a_block') + expect(tags[0].text).to eq('The block parameter.') + expect(tags[0].types).to eq(['Callable']) + tags = object.docstring.tags(:return) + expect(tags.size).to eq(1) + expect(tags[0].name).to be_nil + expect(tags[0].text).to eq('Returns nothing.') + expect(tags[0].types).to eq(['Undef']) + tags = object.docstring.tags(:api) + expect(tags.size).to eq(1) + expect(tags[0].text).to eq('public') + end + end + + describe 'parsing a function with a block param with two parameter' do + let(:source) { <<-SOURCE +# An example 4.x function. +Puppet::Functions.create_function(:foo) do + # @param a_block The block parameter. + # @return [Undef] Returns nothing. + dispatch :foo do + optional_block_param 'Callable[String]', :a_block + end +end + SOURCE + } + + it 'should register a function object with the expected parameters' do + expect(subject.size).to eq(1) + object = subject.first + expect(object).to be_a(PuppetStrings::Yard::CodeObjects::Function) + expect(object.namespace).to eq(PuppetStrings::Yard::CodeObjects::Functions.instance(PuppetStrings::Yard::CodeObjects::Function::RUBY_4X)) + expect(object.name).to eq(:foo) + expect(object.signature).to eq('foo(Optional[Callable[String]] &$a_block)') + expect(object.parameters).to eq([['&a_block', nil]]) + expect(object.docstring).to eq('An example 4.x function.') + expect(object.docstring.tags(:overload).empty?).to be_truthy + expect(object.docstring.tags.size).to eq(3) + tags = object.docstring.tags(:param) + expect(tags.size).to eq(1) + expect(tags[0].name).to eq('&a_block') + expect(tags[0].text).to eq('The block parameter.') + expect(tags[0].types).to eq(['Optional[Callable[String]]']) + tags = object.docstring.tags(:return) + expect(tags.size).to eq(1) + expect(tags[0].name).to be_nil + expect(tags[0].text).to eq('Returns nothing.') + expect(tags[0].types).to eq(['Undef']) + tags = object.docstring.tags(:api) + expect(tags.size).to eq(1) + expect(tags[0].text).to eq('public') + end + end + end + + describe 'parsing a function with a multiple dispatches' do + let(:source) { <<-SOURCE +# An example 4.x function. +Puppet::Functions.create_function(:foo) do + # The first overload. + # @param param1 The first parameter. + # @param param2 The second parameter. + # @param param3 The third parameter. + # @return [Undef] Returns nothing. + dispatch :foo do + param 'Integer', :param1 + param 'Any', :param2 + optional_param 'Array[String]', :param3 + end + + # The second overload. + # @param param The first parameter. + # @param block The block parameter. + # @return [String] Returns a string. + dispatch :other do + param 'Boolean', :param + block_param + end + + def foo(param1, param2, param3 = nil) + end + + def other(b) + 'lol' + end +end +SOURCE + } + + it 'should register a function object with overload tags' do + expect(subject.size).to eq(1) + object = subject.first + expect(object).to be_a(PuppetStrings::Yard::CodeObjects::Function) + expect(object.namespace).to eq(PuppetStrings::Yard::CodeObjects::Functions.instance(PuppetStrings::Yard::CodeObjects::Function::RUBY_4X)) + expect(object.name).to eq(:foo) + expect(object.signature).to eq('') + expect(object.parameters).to eq([]) + expect(object.docstring).to eq('An example 4.x function.') + expect(object.docstring.tags(:param).empty?).to be_truthy + expect(object.docstring.tags(:return).empty?).to be_truthy + expect(object.docstring.tags.size).to eq(3) + overloads = object.docstring.tags(:overload) + expect(overloads.size).to eq(2) + expect(overloads[0]).to be_a(PuppetStrings::Yard::Tags::OverloadTag) + expect(overloads[0].docstring).to eq('The first overload.') + expect(overloads[0].signature).to eq('foo(Integer $param1, Any $param2, Optional[Array[String]] $param3)') + expect(overloads[0].tags.size).to eq(4) + tags = overloads[0].tags(:param) + expect(tags.size).to eq(3) + expect(tags[0].name).to eq('param1') + expect(tags[0].text).to eq('The first parameter.') + expect(tags[0].types).to eq(['Integer']) + expect(tags[1].name).to eq('param2') + expect(tags[1].text).to eq('The second parameter.') + expect(tags[1].types).to eq(['Any']) + expect(tags[2].name).to eq('param3') + expect(tags[2].text).to eq('The third parameter.') + expect(tags[2].types).to eq(['Optional[Array[String]]']) + tags = overloads[0].tags(:return) + expect(tags.size).to eq(1) + expect(tags[0].name).to be_nil + expect(tags[0].text).to eq('Returns nothing.') + expect(tags[0].types).to eq(['Undef']) + expect(overloads[1]).to be_a(PuppetStrings::Yard::Tags::OverloadTag) + expect(overloads[1].docstring).to eq('The second overload.') + expect(overloads[1].signature).to eq('foo(Boolean $param, Callable &$block)') + expect(overloads[1].tags.size).to eq(3) + tags = overloads[1].tags(:param) + expect(tags.size).to eq(2) + expect(tags[0].name).to eq('param') + expect(tags[0].text).to eq('The first parameter.') + expect(tags[0].types).to eq(['Boolean']) + expect(tags[1].name).to eq('&block') + expect(tags[1].text).to eq('The block parameter.') + expect(tags[1].types).to eq(['Callable']) + tags = overloads[1].tags(:return) + expect(tags.size).to eq(1) + expect(tags[0].name).to be_nil + expect(tags[0].text).to eq('Returns a string.') + expect(tags[0].types).to eq(['String']) + tags = object.docstring.tags(:api) + expect(tags.size).to eq(1) + expect(tags[0].text).to eq('public') + end + end + + describe 'parsing a function with a missing parameter' do + let(:source) { <<-SOURCE +# An example 4.x function. +Puppet::Functions.create_function(:foo) do + # @param missing A missing parameter. + # @return [Undef] Returns nothing. + dispatch :foo do + end +end +SOURCE + } + + it 'should output a warning' do + expect{ subject }.to output(/\[warn\]: The @param tag for parameter 'missing' has no matching parameter at \(stdin\):5/).to_stdout_from_any_process + end + end + + describe 'parsing a function with a missing @param tag' do + let(:source) { <<-SOURCE +# An example 4.x function. +Puppet::Functions.create_function(:foo) do + # @return [Undef] Returns nothing. + dispatch :foo do + param 'String', :param1 + end +end +SOURCE + } + + it 'should output a warning' do + expect{ subject }.to output(/\[warn\]: Missing @param tag for parameter 'param1' near \(stdin\):5/).to_stdout_from_any_process + end + end + + describe 'parsing a function with a typed @param tag' do + let(:source) { <<-SOURCE +# An example 4.x function. +Puppet::Functions.create_function(:foo) do + # @param [Integer] param1 The first parameter. + # @return [Undef] Returns nothing. + dispatch :foo do + param 'String', :param1 + end +end +SOURCE + } + + it 'should output a warning' do + expect{ subject }.to output(/\[warn\]: The @param tag for parameter 'param1' should not contain a type specification near \(stdin\):6: ignoring in favor of dispatch type information\./).to_stdout_from_any_process + end + end + + describe 'parsing a function with a typed @param tag' do + let(:source) { <<-SOURCE +# An example 4.x function. +Puppet::Functions.create_function(:foo) do + # @param param1 The first parameter. + dispatch :foo do + param 'String', :param1 + end +end +SOURCE + } + + it 'should output a warning' do + expect{ subject }.to output(/\[warn\]: Missing @return tag near \(stdin\):4/).to_stdout_from_any_process + end + end + + describe 'parsing a function with a root @param tag' do + let(:source) { <<-SOURCE +# An example 4.x function. +# @param param Nope. +Puppet::Functions.create_function(:foo) do + # @return [Undef] + dispatch :foo do + end +end +SOURCE + } + + it 'should output a warning' do + expect{ subject }.to output(/\[warn\]: The docstring for Puppet 4.x function 'foo' contains @param tags near \(stdin\):3: parameter documentation should be made on the dispatch call\./).to_stdout_from_any_process + end + end + + + describe 'parsing a function with a root @overload tag' do + let(:source) { <<-SOURCE +# An example 4.x function. +# @overload foo +Puppet::Functions.create_function(:foo) do + # @return [Undef] + dispatch :foo do + end +end + SOURCE + } + + it 'should output a warning' do + expect{ subject }.to output(/\[warn\]: The docstring for Puppet 4.x function 'foo' contains @overload tags near \(stdin\):3: overload tags are automatically generated from the dispatch calls\./).to_stdout_from_any_process + end + end + + describe 'parsing a function with a root @return tag' do + let(:source) { <<-SOURCE +# An example 4.x function. +# @return [Undef] foo +Puppet::Functions.create_function(:foo) do + # @return [Undef] + dispatch :foo do + end +end + SOURCE + } + + it 'should output a warning' do + expect{ subject }.to output(/\[warn\]: The docstring for Puppet 4.x function 'foo' contains @return tags near \(stdin\):3: return value documentation should be made on the dispatch call\./).to_stdout_from_any_process + end + end +end diff --git a/spec/unit/puppet-strings/yard/handlers/ruby/provider_handler_spec.rb b/spec/unit/puppet-strings/yard/handlers/ruby/provider_handler_spec.rb new file mode 100644 index 0000000..9ae6b8e --- /dev/null +++ b/spec/unit/puppet-strings/yard/handlers/ruby/provider_handler_spec.rb @@ -0,0 +1,62 @@ +require 'spec_helper' +require 'puppet-strings/yard' + +describe PuppetStrings::Yard::Handlers::Ruby::ProviderHandler do + subject { + YARD::Parser::SourceParser.parse_string(source, :ruby) + YARD::Registry.all(:puppet_provider) + } + + describe 'parsing source without a provider definition' do + let(:source) { 'puts "hi"' } + + it 'no providers should be in the registry' do + expect(subject.empty?).to eq(true) + end + end + + describe 'parsing a provider with a missing description' do + let(:source) { <<-SOURCE +Puppet::Type.type(:custom).provide :linux do +end +SOURCE + } + + it 'should log a warning' do + expect{ subject }.to output(/\[warn\]: Missing a description for Puppet provider 'linux' \(resource type 'custom'\) at \(stdin\):1\./).to_stdout_from_any_process + end + end + + describe 'parsing a provider definition' do + let(:source) { <<-SOURCE +Puppet::Type.type(:custom).provide :linux do + desc 'An example provider on Linux.' + confine kernel: 'Linux' + confine osfamily: 'RedHat' + defaultfor kernel: 'Linux' + has_feature :implements_some_feature + has_feature :some_other_feature + commands foo: /usr/bin/foo +end +SOURCE + } + + it 'should register a provider object' do + expect(subject.size).to eq(1) + object = subject.first + expect(object).to be_a(PuppetStrings::Yard::CodeObjects::Provider) + expect(object.namespace).to eq(PuppetStrings::Yard::CodeObjects::Providers.instance('custom')) + expect(object.name).to eq(:linux) + expect(object.type_name).to eq('custom') + expect(object.docstring).to eq('An example provider on Linux.') + expect(object.docstring.tags.size).to eq(1) + tags = object.docstring.tags(:api) + expect(tags.size).to eq(1) + expect(tags[0].text).to eq('public') + expect(object.confines).to eq({ 'kernel' => 'Linux', 'osfamily' => 'RedHat'}) + expect(object.defaults).to eq({ 'kernel' => 'Linux'}) + expect(object.features).to eq(['implements_some_feature', 'some_other_feature']) + expect(object.commands).to eq({'foo' => '/usr/bin/foo'}) + end + end +end diff --git a/spec/unit/puppet-strings/yard/handlers/ruby/type_handler_spec.rb b/spec/unit/puppet-strings/yard/handlers/ruby/type_handler_spec.rb new file mode 100644 index 0000000..ec0894b --- /dev/null +++ b/spec/unit/puppet-strings/yard/handlers/ruby/type_handler_spec.rb @@ -0,0 +1,126 @@ +require 'spec_helper' +require 'puppet-strings/yard' + +describe PuppetStrings::Yard::Handlers::Ruby::TypeHandler do + subject { + YARD::Parser::SourceParser.parse_string(source, :ruby) + YARD::Registry.all(:puppet_type) + } + + describe 'parsing source without a type definition' do + let(:source) { 'puts "hi"' } + + it 'no types should be in the registry' do + expect(subject.empty?).to eq(true) + end + end + + describe 'parsing a type with a missing description' do + let(:source) { <<-SOURCE +Puppet::Type.newtype(:database) do +end +SOURCE + } + + it 'should log a warning' do + expect{ subject }.to output(/\[warn\]: Missing a description for Puppet resource type 'database' at \(stdin\):1\./).to_stdout_from_any_process + end + end + + describe 'parsing a type definition' do + let(:source) { <<-SOURCE +Puppet::Type.newtype(:database) do + desc 'An example database server resource type.' + feature :encryption, 'The provider supports encryption.', methods: [:encrypt] + ensurable do + desc 'What state the database should be in.' + defaultvalues + aliasvalue(:up, :present) + aliasvalue(:down, :absent) + defaultto :up + end + + newparam(:address) do + isnamevar + desc 'The database server name.' + end + + newparam(:encryption_key, required_features: :encryption) do + desc 'The encryption key to use.' + end + + newparam(:encrypt, :parent => Puppet::Parameter::Boolean) do + desc 'Whether or not to encrypt the database.' + defaultto false + end + + newproperty(:file) do + desc 'The database file to use.' + end + + newproperty(:log_level) do + desc 'The log level to use.' + newvalue(:debug) + newvalue(:warn) + newvalue(:error) + defaultto 'warn' + end +end +SOURCE + } + + it 'should register a type object' do + expect(subject.size).to eq(1) + object = subject.first + expect(object).to be_a(PuppetStrings::Yard::CodeObjects::Type) + expect(object.namespace).to eq(PuppetStrings::Yard::CodeObjects::Types.instance) + expect(object.name).to eq(:database) + expect(object.docstring).to eq('An example database server resource type.') + expect(object.docstring.tags.size).to eq(1) + tags = object.docstring.tags(:api) + expect(tags.size).to eq(1) + expect(tags[0].text).to eq('public') + expect(object.properties.size).to eq(3) + expect(object.properties[0].name).to eq('ensure') + expect(object.properties[0].docstring).to eq('What state the database should be in.') + expect(object.properties[0].isnamevar).to eq(false) + expect(object.properties[0].default).to eq('up') + expect(object.properties[0].values).to eq(%w(present absent up down)) + expect(object.properties[0].aliases).to eq({ 'down' => 'absent', 'up' => 'present' }) + expect(object.properties[1].name).to eq('file') + expect(object.properties[1].docstring).to eq('The database file to use.') + expect(object.properties[1].isnamevar).to eq(false) + expect(object.properties[1].default).to be_nil + expect(object.properties[1].values).to eq([]) + expect(object.properties[1].aliases).to eq({}) + expect(object.properties[2].name).to eq('log_level') + expect(object.properties[2].docstring).to eq('The log level to use.') + expect(object.properties[2].isnamevar).to eq(false) + expect(object.properties[2].default).to eq('warn') + expect(object.properties[2].values).to eq(%w(debug warn error)) + expect(object.properties[2].aliases).to eq({}) + expect(object.parameters.size).to eq(3) + expect(object.parameters[0].name).to eq('address') + expect(object.parameters[0].docstring).to eq('The database server name.') + expect(object.parameters[0].isnamevar).to eq(true) + expect(object.parameters[0].default).to be_nil + expect(object.parameters[0].values).to eq([]) + expect(object.parameters[0].aliases).to eq({}) + expect(object.parameters[1].name).to eq('encryption_key') + expect(object.parameters[1].docstring).to eq('The encryption key to use.') + expect(object.parameters[1].isnamevar).to eq(false) + expect(object.parameters[1].default).to be_nil + expect(object.parameters[1].values).to eq([]) + expect(object.parameters[1].aliases).to eq({}) + expect(object.parameters[2].name).to eq('encrypt') + expect(object.parameters[2].docstring).to eq('Whether or not to encrypt the database.') + expect(object.parameters[2].isnamevar).to eq(false) + expect(object.parameters[2].default).to eq('false') + expect(object.parameters[2].values).to eq(%w(true false yes no)) + expect(object.parameters[2].aliases).to eq({}) + expect(object.features.size).to eq(1) + expect(object.features[0].name).to eq('encryption') + expect(object.features[0].docstring).to eq('The provider supports encryption.') + end + end +end diff --git a/spec/unit/puppet-strings/yard/parsers/puppet/parser_spec.rb b/spec/unit/puppet-strings/yard/parsers/puppet/parser_spec.rb new file mode 100644 index 0000000..12ea9d5 --- /dev/null +++ b/spec/unit/puppet-strings/yard/parsers/puppet/parser_spec.rb @@ -0,0 +1,171 @@ +require 'spec_helper' +require 'puppet-strings/yard' + +describe PuppetStrings::Yard::Parsers::Puppet::Parser do + subject { PuppetStrings::Yard::Parsers::Puppet::Parser.new(source, file) } + let(:file) { 'test.pp' } + + describe 'initialization of the parser' do + let(:source) { 'notice hi' } + + it 'should store the original source' do + expect(subject.source).to eq(source) + end + + it 'should store the original file name' do + expect(subject.file).to eq(file) + end + + it 'should have no relevant statements' do + subject.parse + expect(subject.enumerator.empty?).to be_truthy + end + end + + describe 'parsing invalid Puppet source code' do + let(:source) { < present + } +} +SOURCE + } + + it 'should only return the class statement' do + subject.parse + expect(subject.enumerator.size).to eq(1) + statement = subject.enumerator.first + expect(statement).to be_a(PuppetStrings::Yard::Parsers::Puppet::ClassStatement) + expect(statement.source).to eq("class foo(Integer $param1, $param2, String $param3 = hi) inherits foo::bar {\n file { '/tmp/foo':\n ensure => present\n }\n}") + expect(statement.file).to eq(file) + expect(statement.line).to eq(6) + expect(statement.docstring).to eq('A simple foo class.') + expect(statement.name).to eq('foo') + expect(statement.parent_class).to eq('foo::bar') + expect(statement.parameters.size).to eq(3) + expect(statement.parameters[0].name).to eq('param1') + expect(statement.parameters[0].type).to eq('Integer') + expect(statement.parameters[0].value).to be_nil + expect(statement.parameters[1].name).to eq('param2') + expect(statement.parameters[1].type).to be_nil + expect(statement.parameters[1].value).to be_nil + expect(statement.parameters[2].name).to eq('param3') + expect(statement.parameters[2].type).to eq('String') + expect(statement.parameters[2].value).to eq('hi') + end + end + + describe 'parsing nested class definitions' do + let(:source) { < present + } +} +SOURCE + } + + it 'should parse the defined type statement' do + subject.parse + expect(subject.enumerator.size).to eq(1) + statement = subject.enumerator.first + expect(statement).to be_a(PuppetStrings::Yard::Parsers::Puppet::DefinedTypeStatement) + expect(statement.name).to eq('foo') + expect(statement.source).to eq("define foo(Integer $param1, $param2, String $param3 = hi) {\n file { '/tmp/foo':\n ensure => present\n }\n}") + expect(statement.file).to eq(file) + expect(statement.line).to eq(6) + expect(statement.docstring).to eq('A simple foo defined type.') + expect(statement.parameters.size).to eq(3) + expect(statement.parameters[0].name).to eq('param1') + expect(statement.parameters[0].type).to eq('Integer') + expect(statement.parameters[0].value).to be_nil + expect(statement.parameters[1].name).to eq('param2') + expect(statement.parameters[1].type).to be_nil + expect(statement.parameters[1].value).to be_nil + expect(statement.parameters[2].name).to eq('param3') + expect(statement.parameters[2].type).to eq('String') + expect(statement.parameters[2].value).to eq('hi') + end + end + + describe 'parsing puppet functions' do + let(:source) { <