Emmanuel Debanne | 5216041 | 2014-09-19 09:20:50 +0200 | [diff] [blame] | 1 | #!/usr/bin/env ruby |
| 2 | # Tests for the automerge plugin. |
| 3 | # |
| 4 | # Required local configuration: |
| 5 | # - gerrit running on 0.0.0.0:29418 |
| 6 | # - 2 cloned projects |
Emmanuel Debanne | 0cf82d7 | 2015-08-20 16:49:14 +0200 | [diff] [blame] | 7 | # - local user must be an owner of these repos |
Emmanuel Debanne | 5216041 | 2014-09-19 09:20:50 +0200 | [diff] [blame] | 8 | |
| 9 | gem "minitest" |
| 10 | require 'minitest/autorun' |
| 11 | require 'json' |
| 12 | require 'open3' |
| 13 | |
| 14 | PROJECTS_DIR="~/" |
| 15 | PROJECT1="project1" |
| 16 | PROJECT2="project2" |
Emmanuel Debanne | ac1b10a | 2017-09-05 11:34:06 +0200 | [diff] [blame] | 17 | USER="admin" |
Emmanuel Debanne | 5216041 | 2014-09-19 09:20:50 +0200 | [diff] [blame] | 18 | |
| 19 | class TestAutomerge < MiniTest::Test |
| 20 | HOST = "0.0.0.0" |
| 21 | PORT = 29418 |
Emmanuel Debanne | ac1b10a | 2017-09-05 11:34:06 +0200 | [diff] [blame] | 22 | GERRIT_SSH = "ssh -l #{USER} -p #{PORT} #{HOST}" |
Emmanuel Debanne | 5216041 | 2014-09-19 09:20:50 +0200 | [diff] [blame] | 23 | |
| 24 | def setup |
| 25 | clean_local_repo(PROJECT1) |
| 26 | clean_local_repo(PROJECT2) |
| 27 | clean_gerrit([PROJECT1, PROJECT2]) |
| 28 | end |
| 29 | |
| 30 | def test_no_topic |
| 31 | commit_id = create_review(PROJECT1, "review0 on #{PROJECT1}") |
| 32 | approve_review(commit_id) |
| 33 | check_status(commit_id, 'MERGED') |
| 34 | end |
| 35 | |
| 36 | def test_normal_topic_1_repo |
| 37 | commit_id = create_review(PROJECT1, "review0 on #{PROJECT1}", "topic1") |
| 38 | check_status(commit_id, 'NEW') |
| 39 | approve_review(commit_id) |
| 40 | check_status(commit_id, 'MERGED') |
| 41 | end |
| 42 | |
| 43 | def test_crossrepo_topic_1_repo |
| 44 | commit_id = create_review(PROJECT1, "review0 on #{PROJECT1}", "crossrepo/topic1") |
| 45 | approve_review(commit_id) |
| 46 | check_status(commit_id, 'MERGED') |
| 47 | end |
| 48 | |
| 49 | def test_crossrepo_topic_1_repo_over_not_merged_commit |
| 50 | commit0 = create_review(PROJECT1, "review0 on #{PROJECT1}") |
| 51 | commit0b = create_review(PROJECT1, "review0b on #{PROJECT1}", "crossrepo/topic1") |
Emmanuel Debanne | f92d6b0 | 2017-09-06 11:24:06 +0200 | [diff] [blame] | 52 | |
Emmanuel Debanne | df78b8c | 2017-09-05 17:20:44 +0200 | [diff] [blame] | 53 | check_last_message_contains(commit0b, "This cross-repo review depends on a not merged commit") |
Emmanuel Debanne | 5216041 | 2014-09-19 09:20:50 +0200 | [diff] [blame] | 54 | end |
| 55 | |
| 56 | def test_normal_topic_2_repos |
| 57 | commit1 = create_review(PROJECT1, "review1 on #{PROJECT1}", "topic2") |
| 58 | commit2 = create_review(PROJECT2, "review2 on #{PROJECT2}", "topic2") |
| 59 | approve_review(commit1) |
| 60 | check_status(commit1, 'MERGED') |
| 61 | check_status(commit2, 'NEW') |
| 62 | approve_review(commit2) |
| 63 | check_status(commit2, 'MERGED') |
| 64 | end |
| 65 | |
| 66 | def test_crossrepo_topic_2_repos |
| 67 | commit1 = create_review(PROJECT1, "review1 on #{PROJECT1}", "crossrepo/topic2") |
| 68 | commit2 = create_review(PROJECT2, "review2 on #{PROJECT2}", "crossrepo/topic2") |
| 69 | approve_review(commit1) |
| 70 | check_status(commit1, 'NEW') |
| 71 | check_status(commit2, 'NEW') |
Emmanuel Debanne | cea7a4e | 2017-09-04 16:31:01 +0200 | [diff] [blame] | 72 | |
Emmanuel Debanne | 5216041 | 2014-09-19 09:20:50 +0200 | [diff] [blame] | 73 | approve_review(commit2) |
Emmanuel Debanne | cea7a4e | 2017-09-04 16:31:01 +0200 | [diff] [blame] | 74 | |
Emmanuel Debanne | 5216041 | 2014-09-19 09:20:50 +0200 | [diff] [blame] | 75 | check_status(commit1, 'MERGED') |
| 76 | check_status(commit2, 'MERGED') |
| 77 | end |
| 78 | |
Emmanuel Debanne | cea7a4e | 2017-09-04 16:31:01 +0200 | [diff] [blame] | 79 | def test_crossrepo_topic_2_repos_above_non_mergeable_commit |
| 80 | commit1a = create_review(PROJECT1, "review1a on #{PROJECT1}") |
| 81 | commit1b = create_review(PROJECT1, "review1b on #{PROJECT1}", "crossrepo/topic2") |
| 82 | commit2 = create_review(PROJECT2, "review2 on #{PROJECT2}", "crossrepo/topic2") |
| 83 | approve_review(commit1b) |
| 84 | check_status(commit1a, 'NEW') |
| 85 | check_status(commit1b, 'NEW') |
| 86 | check_status(commit2, 'NEW') |
| 87 | |
| 88 | approve_review(commit2) |
| 89 | |
Emmanuel Debanne | 5e8df61 | 2017-09-05 17:24:45 +0200 | [diff] [blame] | 90 | check_last_message_contains(commit2, "blocked by a non merged commit below") |
Emmanuel Debanne | cea7a4e | 2017-09-04 16:31:01 +0200 | [diff] [blame] | 91 | check_status(commit1a, 'NEW') |
| 92 | check_status(commit1b, 'NEW') |
| 93 | check_status(commit2, 'NEW') |
| 94 | end |
| 95 | |
Emmanuel Debanne | ae7802c | 2017-09-05 11:53:20 +0200 | [diff] [blame] | 96 | def test_crossrepo_topic_2_repos_below_not_merged_commit |
| 97 | commit1 = create_review(PROJECT1, "review1 on #{PROJECT1}", "crossrepo/topic2") |
| 98 | commit1b = create_review(PROJECT1, "review1b on #{PROJECT1}") |
| 99 | commit2 = create_review(PROJECT2, "review2 on #{PROJECT2}", "crossrepo/topic2") |
| 100 | approve_review(commit1) |
| 101 | check_status(commit1, 'NEW') |
| 102 | check_status(commit2, 'NEW') |
| 103 | |
| 104 | approve_review(commit2) |
| 105 | |
| 106 | check_status(commit1, 'MERGED') |
| 107 | check_status(commit2, 'MERGED') |
| 108 | end |
| 109 | |
Emmanuel Debanne | ac248a2 | 2017-09-04 15:01:30 +0200 | [diff] [blame] | 110 | def test_refupdatedevent_merge_upper_commit |
| 111 | commit1a = create_review(PROJECT1, "review1a on #{PROJECT1}") |
| 112 | commit1b = create_review(PROJECT1, "review1b on #{PROJECT1}") |
| 113 | approve_review(commit1b) |
| 114 | check_status(commit1a, 'NEW') |
| 115 | check_status(commit1b, 'NEW') |
| 116 | |
| 117 | approve_review(commit1a) |
| 118 | |
| 119 | check_status(commit1a, 'MERGED') |
| 120 | check_status(commit1b, 'MERGED') |
| 121 | end |
| 122 | |
Emmanuel Debanne | cea7a4e | 2017-09-04 16:31:01 +0200 | [diff] [blame] | 123 | def test_refupdatedevent_merge_upper_crossrepo |
| 124 | commit1a = create_review(PROJECT1, "review1a on #{PROJECT1}") |
| 125 | commit1b = create_review(PROJECT1, "review1b on #{PROJECT1}", "crossrepo/topic2") |
| 126 | commit2 = create_review(PROJECT2, "review2 on #{PROJECT2}", "crossrepo/topic2") |
| 127 | approve_review(commit1b) |
| 128 | approve_review(commit2) |
| 129 | check_status(commit1a, 'NEW') |
| 130 | check_status(commit1b, 'NEW') |
| 131 | check_status(commit2, 'NEW') |
| 132 | |
| 133 | approve_review(commit1a) |
| 134 | |
| 135 | check_status(commit1a, 'MERGED') |
| 136 | check_status(commit1b, 'MERGED') |
| 137 | check_status(commit2, 'MERGED') |
| 138 | end |
| 139 | |
Emmanuel Debanne | ac248a2 | 2017-09-04 15:01:30 +0200 | [diff] [blame] | 140 | def test_refupdatedevent_does_not_merge_non_mergeable_upper_crossrepo |
| 141 | commit1a = create_review(PROJECT1, "review1a on #{PROJECT1}") |
| 142 | commit1b = create_review(PROJECT1, "review1b on #{PROJECT1}", "crossrepo/topic2") |
| 143 | commit2 = create_review(PROJECT2, "review2 on #{PROJECT2}", "crossrepo/topic2") |
| 144 | approve_review(commit1b) |
| 145 | check_status(commit1a, 'NEW') |
| 146 | check_status(commit1b, 'NEW') |
| 147 | check_status(commit2, 'NEW') |
| 148 | |
| 149 | approve_review(commit1a) |
| 150 | |
| 151 | check_status(commit1a, 'MERGED') |
| 152 | check_status(commit1b, 'NEW') |
| 153 | check_status(commit2, 'NEW') |
| 154 | end |
| 155 | |
Emmanuel Debanne | 7f56b16 | 2014-12-29 09:29:25 +0100 | [diff] [blame] | 156 | def test_two_reviews_with_same_changed_id |
| 157 | commit1 = create_review(PROJECT1, "review1 on #{PROJECT1}") |
| 158 | change_id = read_change_id(PROJECT1) |
| 159 | abandon_review(commit1) |
| 160 | # Reuse Change-Id of abandoned review |
| 161 | commit2 = create_review(PROJECT2, "review2 on #{PROJECT2}", nil, change_id) |
| 162 | |
| 163 | approve_review(commit2) |
| 164 | |
| 165 | check_status(commit2, 'MERGED') |
| 166 | end |
| 167 | |
Emmanuel Debanne | 5216041 | 2014-09-19 09:20:50 +0200 | [diff] [blame] | 168 | private |
| 169 | |
| 170 | def project_dir(project_name) |
| 171 | "#{PROJECTS_DIR}#{project_name}" |
| 172 | end |
| 173 | |
| 174 | def clean_local_repo(project_name) |
| 175 | execute("cd #{project_dir(project_name)} && git fetch && git reset --hard FETCH_HEAD") |
| 176 | end |
| 177 | |
| 178 | def clean_gerrit(projects) |
| 179 | projects_query = projects.map{|project| "project:#{project}" }.join(" OR ") |
| 180 | query = "status:open AND (#{projects_query})" |
| 181 | reviews = gerrit_query(query) |
| 182 | reviews.each do |review| |
| 183 | review_number = review['number'] |
Paladox none | cc5b3d9 | 2017-10-10 13:17:04 +0000 | [diff] [blame] | 184 | execute("#{GERRIT_SSH} gerrit review --abandon #{review_number},1") |
Emmanuel Debanne | 5216041 | 2014-09-19 09:20:50 +0200 | [diff] [blame] | 185 | end |
| 186 | end |
| 187 | |
Emmanuel Debanne | 7f56b16 | 2014-12-29 09:29:25 +0100 | [diff] [blame] | 188 | def read_change_id(project_name, commit_id = "HEAD") |
| 189 | change_id = execute(["cd #{project_dir(project_name)}", |
| 190 | "git show #{commit_id} | grep Change-Id | sed 's/^.*Change-Id: \\([Ia-f0-9]*\\)$/\\1/'" |
| 191 | ].join(" && ")) |
| 192 | refute(change_id.empty?, "missing change-id") |
| 193 | change_id |
| 194 | end |
| 195 | |
Paladox none | cc5b3d9 | 2017-10-10 13:17:04 +0000 | [diff] [blame] | 196 | def create_review(project_name, message, topic = nil, change_id = nil) |
Emmanuel Debanne | 5216041 | 2014-09-19 09:20:50 +0200 | [diff] [blame] | 197 | topic_suffix = "/#{topic}" if topic |
Emmanuel Debanne | 7f56b16 | 2014-12-29 09:29:25 +0100 | [diff] [blame] | 198 | message = "#{message}\n\nChange-Id: #{change_id}" if change_id |
Emmanuel Debanne | 5216041 | 2014-09-19 09:20:50 +0200 | [diff] [blame] | 199 | execute(["cd #{project_dir(project_name)}", |
| 200 | "echo 0 >> a", |
Emmanuel Debanne | 0cf82d7 | 2015-08-20 16:49:14 +0200 | [diff] [blame] | 201 | "git add .", |
| 202 | %Q(git commit -m "#{message}"), |
Paladox none | cc5b3d9 | 2017-10-10 13:17:04 +0000 | [diff] [blame] | 203 | "git push origin HEAD:refs/for/master#{topic_suffix}" |
Emmanuel Debanne | 5216041 | 2014-09-19 09:20:50 +0200 | [diff] [blame] | 204 | ].join(" && ")) |
| 205 | commit_id = execute("cd #{project_dir(project_name)} && git rev-parse HEAD") |
| 206 | refute(commit_id.empty?, "missing commit-id") |
| 207 | commit_id |
| 208 | end |
| 209 | |
| 210 | def approve_review(commit_id) |
Emmanuel Debanne | c37f0a1 | 2017-08-29 22:32:53 +0200 | [diff] [blame] | 211 | execute("#{GERRIT_SSH} gerrit review --strict-labels --verified 1 --code-review 2 #{commit_id}") |
Emmanuel Debanne | 5216041 | 2014-09-19 09:20:50 +0200 | [diff] [blame] | 212 | end |
| 213 | |
Emmanuel Debanne | 7f56b16 | 2014-12-29 09:29:25 +0100 | [diff] [blame] | 214 | def abandon_review(commit_id) |
| 215 | execute("#{GERRIT_SSH} gerrit review --abandon #{commit_id}") |
| 216 | end |
| 217 | |
Emmanuel Debanne | 5216041 | 2014-09-19 09:20:50 +0200 | [diff] [blame] | 218 | def check_status(commit_id, expected_status) |
| 219 | reviews = gerrit_query("commit:#{commit_id}") |
| 220 | assert_equal(1, reviews.size, "missing review with commit #{commit_id}") |
| 221 | review = reviews[0] |
Emmanuel Debanne | c37f0a1 | 2017-08-29 22:32:53 +0200 | [diff] [blame] | 222 | assert_equal(expected_status, review['status'], "wrong status on review #{review['number']} '#{review['subject']}'") |
Emmanuel Debanne | 5216041 | 2014-09-19 09:20:50 +0200 | [diff] [blame] | 223 | end |
| 224 | |
| 225 | def check_label(commit_id, label_name, expected_label_value) |
| 226 | reviews = gerrit_query("commit:#{commit_id}", "--all-approvals") |
| 227 | assert_equal(1, reviews.size, "missing review with commit #{commit_id}") |
| 228 | review = reviews[0] |
Emmanuel Debanne | c37f0a1 | 2017-08-29 22:32:53 +0200 | [diff] [blame] | 229 | approvals = review['patchSets'][0]['approvals'] |
| 230 | refute(approvals.nil?, "No approval on #{commit_id}") |
| 231 | code_review_approvals = approvals.select {|ap| ap['description'] == "Code-Review"} |
| 232 | refute(code_review_approvals.empty?, "No code-review score on #{commit_id}") |
Emmanuel Debanne | 5216041 | 2014-09-19 09:20:50 +0200 | [diff] [blame] | 233 | assert_equal(expected_label_value, code_review_approvals[0]['value'], "wrong label on review: #{review['number']}") |
| 234 | end |
| 235 | |
Emmanuel Debanne | 0cf82d7 | 2015-08-20 16:49:14 +0200 | [diff] [blame] | 236 | def check_last_message_contains(commit_id, expected_content) |
| 237 | reviews = gerrit_query("commit:#{commit_id}", "--comments") |
| 238 | assert_equal(1, reviews.size, "missing review with commit #{commit_id}") |
| 239 | messages = reviews[0]['comments'].map{|comment| comment['message'] } |
| 240 | assert(messages.last.include?(expected_content), "missing comment containing '#{expected_content}'") |
| 241 | end |
Emmanuel Debanne | 5216041 | 2014-09-19 09:20:50 +0200 | [diff] [blame] | 242 | |
| 243 | def gerrit_query(query, options = "") |
| 244 | jsons = `#{GERRIT_SSH} gerrit query --format JSON #{options} '#{query}' | grep -v type\\"\\:\\"stats` |
| 245 | hashes = [] |
| 246 | jsons.each_line do |line| |
| 247 | hashes << JSON.parse(line) |
| 248 | end |
| 249 | hashes |
| 250 | end |
| 251 | |
| 252 | # Run a command as a child process and wait for its end. |
| 253 | def execute(command, opts={}) |
| 254 | _, stdout, stderr, wait_thr = Open3.popen3(command, opts) |
| 255 | if wait_thr.value != 0 |
| 256 | puts "Command failed: #{command}: stdout: #{stdout.read}, stderr: #{stderr.read}" |
| 257 | raise RuntimeError, stderr.read |
| 258 | end |
| 259 | return stdout.read |
| 260 | end |
| 261 | end |