jujube 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.travis.yml +9 -0
  4. data/.yardopts +4 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE +22 -0
  7. data/README.md +180 -0
  8. data/Rakefile +15 -0
  9. data/acceptance/fixtures/endToEnd/endToEnd.job +43 -0
  10. data/acceptance/fixtures/endToEnd/expected.yml +80 -0
  11. data/acceptance/fixtures/multipleFiles/job1.job +1 -0
  12. data/acceptance/fixtures/multipleFiles/job2.job +1 -0
  13. data/acceptance/fixtures/multipleJobs/test.job +2 -0
  14. data/acceptance/fixtures/nestedDirectories/job1.job +1 -0
  15. data/acceptance/fixtures/nestedDirectories/nested/job2.job +1 -0
  16. data/acceptance/fixtures/singleJob/test.job +3 -0
  17. data/acceptance/jujube/acceptance_test.rb +47 -0
  18. data/acceptance/jujube/end_to_end_test.rb +13 -0
  19. data/acceptance/jujube/usage_test.rb +90 -0
  20. data/bin/jujube +5 -0
  21. data/jujube.gemspec +29 -0
  22. data/lib/jujube.rb +6 -0
  23. data/lib/jujube/components.rb +23 -0
  24. data/lib/jujube/components/builders.rb +19 -0
  25. data/lib/jujube/components/helpers.rb +40 -0
  26. data/lib/jujube/components/macros.rb +31 -0
  27. data/lib/jujube/components/notifications.rb +11 -0
  28. data/lib/jujube/components/parameters.rb +50 -0
  29. data/lib/jujube/components/publishers.rb +97 -0
  30. data/lib/jujube/components/scm.rb +19 -0
  31. data/lib/jujube/components/triggers.rb +105 -0
  32. data/lib/jujube/components/wrappers.rb +28 -0
  33. data/lib/jujube/driver.rb +78 -0
  34. data/lib/jujube/dsl.rb +43 -0
  35. data/lib/jujube/job.rb +226 -0
  36. data/lib/jujube/job_file_generator.rb +17 -0
  37. data/lib/jujube/job_loader.rb +51 -0
  38. data/lib/jujube/macros.rb +35 -0
  39. data/lib/jujube/utils.rb +27 -0
  40. data/lib/jujube/version.rb +5 -0
  41. data/test/components/builders_test.rb +10 -0
  42. data/test/components/parameters_test.rb +15 -0
  43. data/test/components/publishers_test.rb +51 -0
  44. data/test/components/scm_test.rb +11 -0
  45. data/test/components/triggers_test.rb +81 -0
  46. data/test/components/wrappers_test.rb +19 -0
  47. data/test/driver_test.rb +47 -0
  48. data/test/job_test.rb +79 -0
  49. data/test/test_helper.rb +4 -0
  50. metadata +186 -0
@@ -0,0 +1,17 @@
1
+ module Jujube
2
+ # Generate a YAML file suitable for jenkins-job-builder.
3
+ class JobFileGenerator
4
+
5
+ # Generate a jenkins-job-builder YAML file for a list of jobs.
6
+ #
7
+ # @param jobs [Array] The job definitions to include in the output.
8
+ # @param output [Pathname] The output file to generate. Any intermediate
9
+ # directories are created automatically.
10
+ def generate(jobs, output)
11
+ output.dirname.mkpath
12
+ output.open("w") do |io|
13
+ jobs.map(&:to_h).to_yaml(io)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,51 @@
1
+ module Jujube
2
+
3
+ # Loads job definitions from a set of files and/or directories.
4
+ class JobLoader
5
+
6
+ # Load job definitions from one or more files and/or directories.
7
+ #
8
+ # The job definition files are loaded as Ruby files; it is expected that
9
+ # will make use of the {#job} DSL function, but they can contain other
10
+ # Ruby code as well.
11
+ #
12
+ # @param pathnames [Pathname...] The file or directory names containing
13
+ # the job definitions.
14
+ def load_jobs(*pathnames)
15
+ Job.all_defined_during do
16
+ pathnames.each do |path|
17
+ load_one(path)
18
+ end
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ # Load jobs from a single file or directory
25
+ #
26
+ # @param path [Pathname] The file or directory to load from.
27
+ def load_one(path)
28
+ if path.directory?
29
+ load_directory(path)
30
+ else
31
+ load_file(path)
32
+ end
33
+ end
34
+
35
+ # Load jobs from all (recursive) files in a directory.
36
+ #
37
+ # @param path [Pathname] The directory to search.
38
+ def load_directory(path)
39
+ path.each_child do |child|
40
+ load_one(child)
41
+ end
42
+ end
43
+
44
+ # Load jobs from a single file.
45
+ #
46
+ # @param path [Pathname] The file to load.
47
+ def load_file(path)
48
+ load(path)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,35 @@
1
+ module Jujube
2
+
3
+ # Macros for defining methods that make it easier to specify the parts
4
+ # a job.
5
+ module Macros
6
+ include Jujube::Utils
7
+
8
+ # A macro that defines an attribute of a job.
9
+ # It generates a reader and a writer for the attribute.
10
+ #
11
+ # @param attribute_name [Symbol] The name of the attribute.
12
+ def attribute(attribute_name)
13
+ canonical_name = canonicalize(attribute_name)
14
+
15
+ define_method attribute_name do
16
+ config[canonical_name]
17
+ end
18
+
19
+ define_method "#{attribute_name}=".to_sym do |value|
20
+ config[canonical_name] = value
21
+ end
22
+ end
23
+
24
+ # A macro that defines a section of a job.
25
+ # It generates a method that returns the `Array` of components
26
+ # in that section.
27
+ #
28
+ # @param section_name [Symbol] The name of the section.
29
+ def section(section_name)
30
+ define_method section_name do
31
+ config[section_name.to_s] ||= []
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,27 @@
1
+ module Jujube
2
+ # Private utility methods.
3
+ module Utils
4
+ private
5
+
6
+ # Convert a hash key into the canonical format required by
7
+ # jenkins-job-builder. Keys must be strings and use
8
+ # dashes (`-`) instead of underscores (`_`).
9
+ #
10
+ # @param key [Symbol] The key to canonicalize.
11
+ # @return [String] The key in canonical format.
12
+ def canonicalize(key)
13
+ key.to_s.gsub("_", "-")
14
+ end
15
+
16
+ # Ensure that all of the keys in an options hash are in
17
+ # canonical jenkins-job-builder format. This method only
18
+ # looks at the top-level keys; it assumes that any
19
+ # nested hashes have already been {#canonicalize}d.
20
+ #
21
+ # @param options [Hash] The options to canonicalize.
22
+ # @return [Hash] The options in canonical format.
23
+ def canonicalize_options(options)
24
+ Hash[options.map { |k, v| [canonicalize(k), v] }]
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,5 @@
1
+ # The main namespace for the Jujube gem.
2
+ module Jujube
3
+ # The current version of the Jujube gem.
4
+ VERSION = "0.5.0"
5
+ end
@@ -0,0 +1,10 @@
1
+ require_relative "../test_helper"
2
+
3
+ class BuildersTest < Minitest::Test
4
+ include Jujube::Components
5
+
6
+ def test_shell
7
+ expected = {'shell' => 'COMMAND'}
8
+ assert_equal(expected, shell('COMMAND'))
9
+ end
10
+ end
@@ -0,0 +1,15 @@
1
+ require_relative "../test_helper"
2
+
3
+ class ParametersTest < Minitest::Test
4
+ include Jujube::Components
5
+
6
+ def test_label_expression_axis
7
+ expected = {"axis" => {"type" => "label-expression", "name" => "arch", "values" => %w{i386 amd64}}}
8
+ assert_equal(expected, label_expression(:arch, %w{i386 amd64}))
9
+ end
10
+
11
+ def test_slave_axis
12
+ expected = {"axis" => {"type" => "slave", "name" => "arch", "values" => %w{i386 amd64}}}
13
+ assert_equal(expected, slave(:arch, %w{i386 amd64}))
14
+ end
15
+ end
@@ -0,0 +1,51 @@
1
+ require_relative "../test_helper"
2
+
3
+ class PublishersTest < Minitest::Test
4
+ include Jujube::Components
5
+
6
+ def test_email_ext
7
+ assert_equal("email-ext", email_ext)
8
+ end
9
+
10
+ def test_email_ext_with_recipients
11
+ expected = {"email-ext" => {"recipients" => "fred, barney"}}
12
+ assert_equal(expected, email_ext(recipients: %w{fred barney}.join(", ")))
13
+ end
14
+
15
+ def test_ircbot
16
+ expected = {"ircbot" => {"notify-start" => true}}
17
+ assert_equal(expected, ircbot(notify_start: true))
18
+ end
19
+
20
+ def test_junit
21
+ expected = {"junit" => {"results" => "RESULTS", "keep-long-stdio" => false}}
22
+ assert_equal(expected, junit(results: "RESULTS", keep_long_stdio: false))
23
+ end
24
+
25
+ def test_archive
26
+ expected = {"archive" => {"artifacts" => "ARTIFACTS", "latest-only" => true, "allow-empty" => true}}
27
+ assert_equal(expected, archive(artifacts: "ARTIFACTS", latest_only: true, allow_empty: true))
28
+ end
29
+
30
+ def test_cppcheck
31
+ expected = {"cppcheck" => {"pattern" => "PATTERN"}}
32
+ assert_equal(expected, cppcheck(pattern: "PATTERN"))
33
+ end
34
+
35
+ def test_xunit
36
+ expected = {"xunit" =>
37
+ {"types" =>
38
+ [{"unittest" => {"pattern" => "PATTERN", "deleteoutput" => false}}]
39
+ }
40
+ }
41
+ actual = xunit do |types|
42
+ types << unittest(pattern: "PATTERN", deleteoutput: false)
43
+ end
44
+ assert_equal(expected, actual)
45
+ end
46
+
47
+ def test_trigger
48
+ expected = {"trigger" => {"project" => "PROJECT"}}
49
+ assert_equal(expected, trigger(project: "PROJECT"))
50
+ end
51
+ end
@@ -0,0 +1,11 @@
1
+ require_relative "../test_helper"
2
+
3
+ class ScmTest < Minitest::Test
4
+ include Jujube::Components
5
+
6
+ def test_git
7
+ expected = {"git" => {"url" => "URL", "branches" => ["master", "dev"], "wipe-workspace" => false}}
8
+ actual = git(url: "URL", branches: %w{master dev}, wipe_workspace: false)
9
+ assert_equal(expected, actual)
10
+ end
11
+ end
@@ -0,0 +1,81 @@
1
+ require_relative "../test_helper"
2
+
3
+ class TriggersTest < Minitest::Test
4
+ include Jujube::Components
5
+
6
+ def test_pollscm
7
+ assert_equal({"pollscm" => "INTERVAL"}, pollscm("INTERVAL"))
8
+ end
9
+
10
+ def test_pollurl_with_no_content_checks
11
+ expected = {"pollurl" =>
12
+ {"cron" => "CRON",
13
+ "urls" => [{"url" => "URL", "check-date" => true}]
14
+ }
15
+ }
16
+ actual = pollurl(cron: "CRON") do |urls|
17
+ urls << url("URL", check_date: true)
18
+ end
19
+ assert_equal(expected, actual)
20
+ end
21
+
22
+ def test_pollurl_with_simple_content
23
+ expected = {"pollurl" =>
24
+ {"urls" =>
25
+ [{"url" => "URL",
26
+ "check-content" => [{"simple" => true}]}]
27
+ }
28
+ }
29
+ actual = pollurl do |urls|
30
+ urls << url("URL") do |content|
31
+ content << simple
32
+ end
33
+ end
34
+ assert_equal(expected, actual)
35
+ end
36
+
37
+ def test_pollurl_with_json_content
38
+ expected = {"pollurl" =>
39
+ {"urls" =>
40
+ [{"url" => "URL",
41
+ "check-content" => [{"json" => %w{JSON_PATH1 JSON_PATH2}}]}]
42
+ }
43
+ }
44
+ actual = pollurl do |urls|
45
+ urls << url("URL") do |content|
46
+ content << json("JSON_PATH1", "JSON_PATH2")
47
+ end
48
+ end
49
+ assert_equal(expected, actual)
50
+ end
51
+
52
+ def test_pollurl_with_xml_content
53
+ expected = {"pollurl" =>
54
+ {"urls" =>
55
+ [{"url" => "URL",
56
+ "check-content" => [{"xml" => %w{XPATH1 XPATH2}}]}]
57
+ }
58
+ }
59
+ actual = pollurl do |urls|
60
+ urls << url("URL") do |content|
61
+ content << xml("XPATH1", "XPATH2")
62
+ end
63
+ end
64
+ assert_equal(expected, actual)
65
+ end
66
+
67
+ def test_pollurl_with_text_content
68
+ expected = {"pollurl" =>
69
+ {"urls" =>
70
+ [{"url" => "URL",
71
+ "check-content" => [{"text" => %w{REGEX1 REGEX2}}]}]
72
+ }
73
+ }
74
+ actual = pollurl do |urls|
75
+ urls << url("URL") do |content|
76
+ content << text("REGEX1", "REGEX2")
77
+ end
78
+ end
79
+ assert_equal(expected, actual)
80
+ end
81
+ end
@@ -0,0 +1,19 @@
1
+ require_relative "../test_helper"
2
+
3
+ class WrappersTest < Minitest::Test
4
+ include Jujube::Components
5
+
6
+ def test_timestamps
7
+ assert_equal('timestamps', timestamps)
8
+ end
9
+
10
+ def test_timeout
11
+ expected = {'timeout' => {'type' => 'elastic', 'fail' => true}}
12
+ assert_equal(expected, timeout(type: 'elastic', fail: true))
13
+ end
14
+
15
+ def test_canonicalizes_timeout_keys
16
+ expected = {'timeout' => {'elastic-percentage' => 150, 'elastic-default-timeout' => 3}}
17
+ assert_equal(expected, timeout(elastic_percentage: 150, elastic_default_timeout: 3))
18
+ end
19
+ end
@@ -0,0 +1,47 @@
1
+ require_relative "test_helper"
2
+ require "flexmock"
3
+
4
+ class DriverTest < Minitest::Test
5
+ include FlexMock::TestCase
6
+
7
+ def setup
8
+ @jobs = [Object.new, Object.new]
9
+ @loader = flexmock("loader", :on, Jujube::JobLoader)
10
+ @generator = flexmock("generator", :on, Jujube::JobFileGenerator)
11
+ @driver = Jujube::Driver.new(@loader, @generator)
12
+ end
13
+
14
+ def test_loads_all_files_in_current_directory_by_default
15
+ @driver.run([])
16
+ assert_spy_called(@loader, :load_jobs, Pathname.getwd)
17
+ end
18
+
19
+ def test_loads_single_file
20
+ @driver.run(%w{test.job})
21
+ assert_spy_called(@loader, :load_jobs, Pathname.new("test.job"))
22
+ end
23
+
24
+ def test_loads_multiple_files
25
+ @driver.run(%w{job1.job job2.job})
26
+ assert_spy_called(@loader, :load_jobs, Pathname.new("job1.job"), Pathname.new("job2.job"))
27
+ end
28
+
29
+ def test_generates_job_files
30
+ @loader.should_receive(:load_jobs => @jobs)
31
+ @driver.run([])
32
+ assert_spy_called(@generator, :generate, @jobs, Pathname.new("jobs.yml"))
33
+ end
34
+
35
+ def test_generates_into_output_file
36
+ @loader.should_receive(:load_jobs => @jobs)
37
+ @driver.run(%w{-o OUTPUT_FILE})
38
+ assert_spy_called(@generator, :generate, @jobs, Pathname.new("OUTPUT_FILE"))
39
+ end
40
+
41
+ def test_handles_mix_of_arguments
42
+ @loader.should_receive(:load_jobs => @jobs)
43
+ @driver.run(%w{job1.job job2.job -o OUTPUT_FILE})
44
+ assert_spy_called(@loader, :load_jobs, Pathname.new("job1.job"), Pathname.new("job2.job"))
45
+ assert_spy_called(@generator, :generate, @jobs, Pathname.new("OUTPUT_FILE"))
46
+ end
47
+ end
data/test/job_test.rb ADDED
@@ -0,0 +1,79 @@
1
+ require_relative "test_helper"
2
+
3
+ class JobTest < Minitest::Test
4
+ include Jujube
5
+
6
+ def setup
7
+ @job = Job.new("testJob")
8
+ end
9
+
10
+ def test_knows_name
11
+ assert_equal("testJob", @job.name)
12
+ end
13
+
14
+ def test_includes_name
15
+ assert_yaml_matches("name: testJob")
16
+ end
17
+
18
+ def test_includes_description
19
+ @job.description = "DESCRIPTION"
20
+ assert_yaml_matches("description: DESCRIPTION")
21
+ end
22
+
23
+ def test_includes_node
24
+ @job.node = "NODE"
25
+ assert_yaml_matches("node: NODE")
26
+ end
27
+
28
+ def test_includes_block_upstream
29
+ @job.block_upstream = true
30
+ assert_yaml_matches("block-upstream: true")
31
+ end
32
+
33
+ def test_includes_block_downstream
34
+ @job.block_downstream = true
35
+ assert_yaml_matches("block-downstream: true")
36
+ end
37
+
38
+ def test_includes_quiet_period
39
+ @job.quiet_period = 42
40
+ assert_yaml_matches("quiet-period: 42")
41
+ end
42
+
43
+ def test_includes_single_item_section
44
+ @job.triggers << "TRIGGER"
45
+ assert_yaml_matches("triggers:\s*\n - TRIGGER")
46
+ end
47
+
48
+ def test_includes_multi_item_section
49
+ @job.wrappers << "FIRST" << "SECOND"
50
+ assert_yaml_matches("wrappers:\s*\n - FIRST\n - SECOND")
51
+ end
52
+
53
+ def test_infers_matrix_project_type_when_axes_specified
54
+ @job.axes << "AXIS"
55
+ assert_yaml_matches("project-type: matrix")
56
+ end
57
+
58
+ def test_renames_scms_to_scm
59
+ @job.scm << "SCM"
60
+ assert_yaml_matches("scm:\s*\n - SCM")
61
+ end
62
+
63
+ def test_self_registers_with_active_registry
64
+ job1 = nil
65
+ job2 = nil
66
+ jobs = Job.all_defined_during do
67
+ job1 = Job.new("job1")
68
+ job2 = Job.new("job2")
69
+ end
70
+ assert_equal([job1, job2], jobs)
71
+ end
72
+
73
+ private
74
+
75
+ def assert_yaml_matches(snippet)
76
+ expected = %r{^job:\s*$.*^ #{snippet}$}m
77
+ assert_match(expected, @job.to_yaml)
78
+ end
79
+ end