/prow/statusreconciler/controller_test.go

https://github.com/kubernetes/test-infra · Go · 1158 lines · 1093 code · 44 blank · 21 comment · 60 complexity · 4fe0afc79cd468a4dd27bdae0453dd7d MD5 · raw file

  1. /*
  2. Copyright 2018 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package statusreconciler
  14. import (
  15. "errors"
  16. "reflect"
  17. "testing"
  18. "k8s.io/apimachinery/pkg/util/diff"
  19. "k8s.io/apimachinery/pkg/util/sets"
  20. "sigs.k8s.io/yaml"
  21. "k8s.io/test-infra/prow/config"
  22. "k8s.io/test-infra/prow/github"
  23. )
  24. func TestAddedBlockingPresubmits(t *testing.T) {
  25. var testCases = []struct {
  26. name string
  27. old, new string
  28. expected map[string][]config.Presubmit
  29. }{
  30. {
  31. name: "no change in blocking presubmits means no added blocking jobs",
  32. old: `"org/repo":
  33. - name: old-job
  34. context: old-context
  35. always_run: true`,
  36. new: `"org/repo":
  37. - name: old-job
  38. context: old-context
  39. always_run: true`,
  40. expected: map[string][]config.Presubmit{
  41. "org/repo": {},
  42. },
  43. },
  44. {
  45. name: "added optional presubmit means no added blocking jobs",
  46. old: `"org/repo":
  47. - name: old-job
  48. context: old-context
  49. always_run: true`,
  50. new: `"org/repo":
  51. - name: old-job
  52. context: old-context
  53. always_run: true
  54. - name: new-job
  55. context: new-context
  56. always_run: true
  57. optional: true`,
  58. expected: map[string][]config.Presubmit{
  59. "org/repo": {},
  60. },
  61. },
  62. {
  63. name: "added non-reporting presubmit means no added blocking jobs",
  64. old: `"org/repo":
  65. - name: old-job
  66. context: old-context
  67. always_run: true`,
  68. new: `"org/repo":
  69. - name: old-job
  70. context: old-context
  71. always_run: true
  72. - name: new-job
  73. context: new-context
  74. always_run: true
  75. skip_report: true`,
  76. expected: map[string][]config.Presubmit{
  77. "org/repo": {},
  78. },
  79. },
  80. {
  81. name: "added presubmit that needs a manual trigger means no added blocking jobs",
  82. old: `"org/repo":
  83. - name: old-job
  84. context: old-context
  85. always_run: true`,
  86. new: `"org/repo":
  87. - name: old-job
  88. context: old-context
  89. always_run: true
  90. - name: new-job
  91. context: new-context
  92. always_run: false`,
  93. expected: map[string][]config.Presubmit{
  94. "org/repo": {},
  95. },
  96. },
  97. {
  98. name: "added required presubmit means added blocking jobs",
  99. old: `"org/repo":
  100. - name: old-job
  101. context: old-context
  102. always_run: true`,
  103. new: `"org/repo":
  104. - name: old-job
  105. context: old-context
  106. always_run: true
  107. - name: new-job
  108. context: new-context
  109. always_run: true`,
  110. expected: map[string][]config.Presubmit{
  111. "org/repo": {{
  112. JobBase: config.JobBase{Name: "new-job"},
  113. Reporter: config.Reporter{
  114. Context: "new-context",
  115. SkipReport: false,
  116. },
  117. AlwaysRun: true,
  118. Optional: false,
  119. }},
  120. },
  121. },
  122. {
  123. name: "optional presubmit transitioning to required means no added blocking jobs",
  124. old: `"org/repo":
  125. - name: old-job
  126. context: old-context
  127. always_run: true
  128. optional: true`,
  129. new: `"org/repo":
  130. - name: old-job
  131. context: old-context
  132. always_run: true`,
  133. expected: map[string][]config.Presubmit{
  134. "org/repo": {},
  135. },
  136. },
  137. {
  138. name: "non-reporting presubmit transitioning to required means added blocking jobs",
  139. old: `"org/repo":
  140. - name: old-job
  141. context: old-context
  142. always_run: true
  143. skip_report: true`,
  144. new: `"org/repo":
  145. - name: old-job
  146. context: old-context
  147. always_run: true`,
  148. expected: map[string][]config.Presubmit{
  149. "org/repo": {{
  150. JobBase: config.JobBase{Name: "old-job"},
  151. Reporter: config.Reporter{Context: "old-context"},
  152. AlwaysRun: true,
  153. }},
  154. },
  155. },
  156. {
  157. name: "required presubmit transitioning run_if_changed means added blocking jobs",
  158. old: `"org/repo":
  159. - name: old-job
  160. context: old-context
  161. run_if_changed: old-changes`,
  162. new: `"org/repo":
  163. - name: old-job
  164. context: old-context
  165. run_if_changed: new-changes`,
  166. expected: map[string][]config.Presubmit{
  167. "org/repo": {{
  168. JobBase: config.JobBase{Name: "old-job"},
  169. Reporter: config.Reporter{Context: "old-context"},
  170. RegexpChangeMatcher: config.RegexpChangeMatcher{RunIfChanged: "new-changes"},
  171. }},
  172. },
  173. },
  174. {
  175. name: "optional presubmit transitioning run_if_changed means no added blocking jobs",
  176. old: `"org/repo":
  177. - name: old-job
  178. context: old-context
  179. run_if_changed: old-changes
  180. optional: true`,
  181. new: `"org/repo":
  182. - name: old-job
  183. context: old-context
  184. run_if_changed: new-changes
  185. optional: true`,
  186. expected: map[string][]config.Presubmit{
  187. "org/repo": {},
  188. },
  189. },
  190. {
  191. name: "optional presubmit transitioning to required run_if_changed means added blocking jobs",
  192. old: `"org/repo":
  193. - name: old-job
  194. context: old-context
  195. always_run: true
  196. optional: true`,
  197. new: `"org/repo":
  198. - name: old-job
  199. context: old-context
  200. run_if_changed: changes`,
  201. expected: map[string][]config.Presubmit{
  202. "org/repo": {{
  203. JobBase: config.JobBase{Name: "old-job"},
  204. Reporter: config.Reporter{Context: "old-context"},
  205. RegexpChangeMatcher: config.RegexpChangeMatcher{RunIfChanged: "changes"},
  206. }},
  207. },
  208. },
  209. {
  210. name: "required presubmit transitioning to new context means no added blocking jobs",
  211. old: `"org/repo":
  212. - name: old-job
  213. context: old-context
  214. always_run: true`,
  215. new: `"org/repo":
  216. - name: old-job
  217. context: new-context
  218. always_run: true`,
  219. expected: map[string][]config.Presubmit{
  220. "org/repo": {},
  221. },
  222. },
  223. }
  224. for _, testCase := range testCases {
  225. t.Run(testCase.name, func(t *testing.T) {
  226. var oldConfig, newConfig map[string][]config.Presubmit
  227. if err := yaml.Unmarshal([]byte(testCase.old), &oldConfig); err != nil {
  228. t.Fatalf("%s: could not unmarshal old config: %v", testCase.name, err)
  229. }
  230. if err := yaml.Unmarshal([]byte(testCase.new), &newConfig); err != nil {
  231. t.Fatalf("%s: could not unmarshal new config: %v", testCase.name, err)
  232. }
  233. if actual, expected := addedBlockingPresubmits(oldConfig, newConfig), testCase.expected; !reflect.DeepEqual(actual, expected) {
  234. t.Errorf("%s: did not get correct added presubmits: %v", testCase.name, diff.ObjectReflectDiff(actual, expected))
  235. }
  236. })
  237. }
  238. }
  239. func TestRemovedBlockingPresubmits(t *testing.T) {
  240. var testCases = []struct {
  241. name string
  242. old, new string
  243. expected map[string][]config.Presubmit
  244. }{
  245. {
  246. name: "no change in blocking presubmits means no removed blocking jobs",
  247. old: `"org/repo":
  248. - name: old-job
  249. context: old-context`,
  250. new: `"org/repo":
  251. - name: old-job
  252. context: old-context`,
  253. expected: map[string][]config.Presubmit{
  254. "org/repo": {},
  255. },
  256. },
  257. {
  258. name: "removed optional presubmit means no removed blocking jobs",
  259. old: `"org/repo":
  260. - name: old-job
  261. context: old-context
  262. optional: true`,
  263. new: `"org/repo": []`,
  264. expected: map[string][]config.Presubmit{
  265. "org/repo": {},
  266. },
  267. },
  268. {
  269. name: "removed non-reporting presubmit means no removed blocking jobs",
  270. old: `"org/repo":
  271. - name: old-job
  272. context: old-context
  273. skip_report: true`,
  274. new: `"org/repo": []`,
  275. expected: map[string][]config.Presubmit{
  276. "org/repo": {},
  277. },
  278. },
  279. {
  280. name: "removed required presubmit means removed blocking jobs",
  281. old: `"org/repo":
  282. - name: old-job
  283. context: old-context`,
  284. new: `"org/repo": []`,
  285. expected: map[string][]config.Presubmit{
  286. "org/repo": {{
  287. JobBase: config.JobBase{Name: "old-job"},
  288. Reporter: config.Reporter{Context: "old-context"},
  289. }},
  290. },
  291. },
  292. {
  293. name: "required presubmit transitioning to optional means no removed blocking jobs",
  294. old: `"org/repo":
  295. - name: old-job
  296. context: old-context`,
  297. new: `"org/repo":
  298. - name: old-job
  299. context: old-context
  300. optional: true`,
  301. expected: map[string][]config.Presubmit{
  302. "org/repo": {},
  303. },
  304. },
  305. {
  306. name: "reporting presubmit transitioning to non-reporting means no removed blocking jobs",
  307. old: `"org/repo":
  308. - name: old-job
  309. context: old-context`,
  310. new: `"org/repo":
  311. - name: old-job
  312. context: old-context
  313. skip_report: true`,
  314. expected: map[string][]config.Presubmit{
  315. "org/repo": {},
  316. },
  317. },
  318. {
  319. name: "all presubmits removed means removed blocking jobs",
  320. old: `"org/repo":
  321. - name: old-job
  322. context: old-context`,
  323. new: `{}`,
  324. expected: map[string][]config.Presubmit{
  325. "org/repo": {{
  326. JobBase: config.JobBase{Name: "old-job"},
  327. Reporter: config.Reporter{Context: "old-context"},
  328. }},
  329. },
  330. },
  331. {
  332. name: "required presubmit transitioning to new context means no removed blocking jobs",
  333. old: `"org/repo":
  334. - name: old-job
  335. context: old-context`,
  336. new: `"org/repo":
  337. - name: old-job
  338. context: new-context`,
  339. expected: map[string][]config.Presubmit{
  340. "org/repo": {},
  341. },
  342. },
  343. {
  344. name: "required presubmit transitioning run_if_changed means no removed blocking jobs",
  345. old: `"org/repo":
  346. - name: old-job
  347. context: old-context
  348. run_if_changed: old-changes`,
  349. new: `"org/repo":
  350. - name: old-job
  351. context: old-context
  352. run_if_changed: new-changes`,
  353. expected: map[string][]config.Presubmit{
  354. "org/repo": {},
  355. },
  356. },
  357. {
  358. name: "optional presubmit transitioning to required run_if_changed means no removed blocking jobs",
  359. old: `"org/repo":
  360. - name: old-job
  361. context: old-context
  362. optional: true`,
  363. new: `"org/repo":
  364. - name: old-job
  365. context: old-context
  366. run_if_changed: changes`,
  367. expected: map[string][]config.Presubmit{
  368. "org/repo": {},
  369. },
  370. },
  371. }
  372. for _, testCase := range testCases {
  373. t.Run(testCase.name, func(t *testing.T) {
  374. var oldConfig, newConfig map[string][]config.Presubmit
  375. if err := yaml.Unmarshal([]byte(testCase.old), &oldConfig); err != nil {
  376. t.Fatalf("%s: could not unmarshal old config: %v", testCase.name, err)
  377. }
  378. if err := yaml.Unmarshal([]byte(testCase.new), &newConfig); err != nil {
  379. t.Fatalf("%s: could not unmarshal new config: %v", testCase.name, err)
  380. }
  381. if actual, expected := removedBlockingPresubmits(oldConfig, newConfig), testCase.expected; !reflect.DeepEqual(actual, expected) {
  382. t.Errorf("%s: did not get correct removed presubmits: %v", testCase.name, diff.ObjectReflectDiff(actual, expected))
  383. }
  384. })
  385. }
  386. }
  387. func TestMigratedBlockingPresubmits(t *testing.T) {
  388. var testCases = []struct {
  389. name string
  390. old, new string
  391. expected map[string][]presubmitMigration
  392. }{
  393. {
  394. name: "no change in blocking presubmits means no migrated blocking jobs",
  395. old: `"org/repo":
  396. - name: old-job
  397. context: old-context`,
  398. new: `"org/repo":
  399. - name: old-job
  400. context: old-context`,
  401. expected: map[string][]presubmitMigration{
  402. "org/repo": {},
  403. },
  404. },
  405. {
  406. name: "removed optional presubmit means no migrated blocking jobs",
  407. old: `"org/repo":
  408. - name: old-job
  409. context: old-context
  410. optional: true`,
  411. new: `"org/repo": []`,
  412. expected: map[string][]presubmitMigration{
  413. "org/repo": {},
  414. },
  415. },
  416. {
  417. name: "removed non-reporting presubmit means no migrated blocking jobs",
  418. old: `"org/repo":
  419. - name: old-job
  420. context: old-context
  421. skip_report: true`,
  422. new: `"org/repo": []`,
  423. expected: map[string][]presubmitMigration{
  424. "org/repo": {},
  425. },
  426. },
  427. {
  428. name: "removed required presubmit means no migrated blocking jobs",
  429. old: `"org/repo":
  430. - name: old-job
  431. context: old-context`,
  432. new: `"org/repo": []`,
  433. expected: map[string][]presubmitMigration{
  434. "org/repo": {},
  435. },
  436. },
  437. {
  438. name: "required presubmit transitioning to optional means no migrated blocking jobs",
  439. old: `"org/repo":
  440. - name: old-job
  441. context: old-context`,
  442. new: `"org/repo":
  443. - name: old-job
  444. context: old-context
  445. optional: true`,
  446. expected: map[string][]presubmitMigration{
  447. "org/repo": {},
  448. },
  449. },
  450. {
  451. name: "reporting presubmit transitioning to non-reporting means no migrated blocking jobs",
  452. old: `"org/repo":
  453. - name: old-job
  454. context: old-context`,
  455. new: `"org/repo":
  456. - name: old-job
  457. context: old-context
  458. skip_report: true`,
  459. expected: map[string][]presubmitMigration{
  460. "org/repo": {},
  461. },
  462. },
  463. {
  464. name: "all presubmits removed means no migrated blocking jobs",
  465. old: `"org/repo":
  466. - name: old-job
  467. context: old-context`,
  468. new: `{}`,
  469. expected: map[string][]presubmitMigration{
  470. "org/repo": {},
  471. },
  472. },
  473. {
  474. name: "required presubmit transitioning to new context means migrated blocking jobs",
  475. old: `"org/repo":
  476. - name: old-job
  477. context: old-context`,
  478. new: `"org/repo":
  479. - name: old-job
  480. context: new-context`,
  481. expected: map[string][]presubmitMigration{
  482. "org/repo": {{
  483. from: config.Presubmit{
  484. JobBase: config.JobBase{Name: "old-job"},
  485. Reporter: config.Reporter{Context: "old-context"},
  486. },
  487. to: config.Presubmit{
  488. JobBase: config.JobBase{Name: "old-job"},
  489. Reporter: config.Reporter{Context: "new-context"},
  490. },
  491. }},
  492. },
  493. },
  494. {
  495. name: "required presubmit transitioning run_if_changed means no removed blocking jobs",
  496. old: `"org/repo":
  497. - name: old-job
  498. context: old-context
  499. run_if_changed: old-changes`,
  500. new: `"org/repo":
  501. - name: old-job
  502. context: old-context
  503. run_if_changed: new-changes`,
  504. expected: map[string][]presubmitMigration{
  505. "org/repo": {},
  506. },
  507. },
  508. {
  509. name: "optional presubmit transitioning to required run_if_changed means no removed blocking jobs",
  510. old: `"org/repo":
  511. - name: old-job
  512. context: old-context
  513. optional: true`,
  514. new: `"org/repo":
  515. - name: old-job
  516. context: old-context
  517. run_if_changed: changes`,
  518. expected: map[string][]presubmitMigration{
  519. "org/repo": {},
  520. },
  521. },
  522. }
  523. for _, testCase := range testCases {
  524. t.Run(testCase.name, func(t *testing.T) {
  525. var oldConfig, newConfig map[string][]config.Presubmit
  526. if err := yaml.Unmarshal([]byte(testCase.old), &oldConfig); err != nil {
  527. t.Fatalf("%s: could not unmarshal old config: %v", testCase.name, err)
  528. }
  529. if err := yaml.Unmarshal([]byte(testCase.new), &newConfig); err != nil {
  530. t.Fatalf("%s: could not unmarshal new config: %v", testCase.name, err)
  531. }
  532. if actual, expected := migratedBlockingPresubmits(oldConfig, newConfig), testCase.expected; !reflect.DeepEqual(actual, expected) {
  533. t.Errorf("%s: did not get correct removed presubmits: %v", testCase.name, diff.ObjectReflectDiff(actual, expected))
  534. }
  535. })
  536. }
  537. }
  538. type orgRepo struct {
  539. org, repo string
  540. }
  541. type orgRepoSet map[orgRepo]interface{}
  542. func (s orgRepoSet) has(item orgRepo) bool {
  543. _, contained := s[item]
  544. return contained
  545. }
  546. type migration struct {
  547. from, to string
  548. }
  549. type migrationSet map[migration]interface{}
  550. func (s migrationSet) insert(items ...migration) {
  551. for _, item := range items {
  552. s[item] = nil
  553. }
  554. }
  555. func (s migrationSet) has(item migration) bool {
  556. _, contained := s[item]
  557. return contained
  558. }
  559. func newFakeMigrator(key orgRepo) fakeMigrator {
  560. return fakeMigrator{
  561. retireErrors: map[orgRepo]sets.String{key: sets.NewString()},
  562. migrateErrors: map[orgRepo]migrationSet{key: {}},
  563. retired: map[orgRepo]sets.String{key: sets.NewString()},
  564. migrated: map[orgRepo]migrationSet{key: {}},
  565. }
  566. }
  567. type fakeMigrator struct {
  568. retireErrors map[orgRepo]sets.String
  569. migrateErrors map[orgRepo]migrationSet
  570. retired map[orgRepo]sets.String
  571. migrated map[orgRepo]migrationSet
  572. }
  573. func (m *fakeMigrator) retire(org, repo, context string, targetBranchFilter func(string) bool) error {
  574. key := orgRepo{org: org, repo: repo}
  575. if contexts, exist := m.retireErrors[key]; exist && contexts.Has(context) {
  576. return errors.New("failed to retire context")
  577. }
  578. if _, exist := m.retired[key]; exist {
  579. m.retired[key].Insert(context)
  580. } else {
  581. m.retired[key] = sets.NewString(context)
  582. }
  583. return nil
  584. }
  585. func (m *fakeMigrator) migrate(org, repo, from, to string, targetBranchFilter func(string) bool) error {
  586. key := orgRepo{org: org, repo: repo}
  587. item := migration{from: from, to: to}
  588. if contexts, exist := m.migrateErrors[key]; exist && contexts.has(item) {
  589. return errors.New("failed to migrate context")
  590. }
  591. if _, exist := m.migrated[key]; exist {
  592. m.migrated[key].insert(item)
  593. } else {
  594. newSet := migrationSet{}
  595. newSet.insert(item)
  596. m.migrated[key] = newSet
  597. }
  598. return nil
  599. }
  600. func newfakeProwJobTriggerer() fakeProwJobTriggerer {
  601. return fakeProwJobTriggerer{
  602. errors: map[prKey]sets.String{},
  603. created: map[prKey]sets.String{},
  604. }
  605. }
  606. type prKey struct {
  607. org, repo string
  608. num int
  609. }
  610. type fakeProwJobTriggerer struct {
  611. errors map[prKey]sets.String
  612. created map[prKey]sets.String
  613. }
  614. func (c *fakeProwJobTriggerer) runAndSkip(pr *github.PullRequest, requestedJobs []config.Presubmit) error {
  615. actions := []struct {
  616. jobs []config.Presubmit
  617. records map[prKey]sets.String
  618. }{
  619. {
  620. jobs: requestedJobs,
  621. records: c.created,
  622. },
  623. }
  624. for _, action := range actions {
  625. names := sets.NewString()
  626. key := prKey{org: pr.Base.Repo.Owner.Login, repo: pr.Base.Repo.Name, num: pr.Number}
  627. for _, job := range action.jobs {
  628. if jobErrors, exists := c.errors[key]; exists && jobErrors.Has(job.Name) {
  629. return errors.New("failed to trigger prow job")
  630. }
  631. names.Insert(job.Name)
  632. }
  633. if current, exists := action.records[key]; exists {
  634. action.records[key] = current.Union(names)
  635. } else {
  636. action.records[key] = names
  637. }
  638. }
  639. return nil
  640. }
  641. func newFakeGitHubClient(key orgRepo) fakeGitHubClient {
  642. return fakeGitHubClient{
  643. prErrors: orgRepoSet{},
  644. refErrors: map[orgRepo]sets.String{key: sets.NewString()},
  645. prs: map[orgRepo][]github.PullRequest{key: {}},
  646. refs: map[orgRepo]map[string]string{key: {}},
  647. }
  648. }
  649. type fakeGitHubClient struct {
  650. prErrors orgRepoSet
  651. refErrors map[orgRepo]sets.String
  652. changeErrors map[orgRepo]sets.Int
  653. prs map[orgRepo][]github.PullRequest
  654. refs map[orgRepo]map[string]string
  655. changes map[orgRepo]map[int][]github.PullRequestChange
  656. }
  657. func (c *fakeGitHubClient) GetPullRequests(org, repo string) ([]github.PullRequest, error) {
  658. key := orgRepo{org: org, repo: repo}
  659. if c.prErrors.has(key) {
  660. return nil, errors.New("failed to get PRs")
  661. }
  662. return c.prs[key], nil
  663. }
  664. func (c *fakeGitHubClient) GetPullRequestChanges(org, repo string, number int) ([]github.PullRequestChange, error) {
  665. key := orgRepo{org: org, repo: repo}
  666. if changes, exist := c.changeErrors[key]; exist && changes.Has(number) {
  667. return nil, errors.New("failed to get changes")
  668. }
  669. return c.changes[key][number], nil
  670. }
  671. func (c *fakeGitHubClient) GetRef(org, repo, ref string) (string, error) {
  672. key := orgRepo{org: org, repo: repo}
  673. if refs, exist := c.refErrors[key]; exist && refs.Has(ref) {
  674. return "", errors.New("failed to get ref")
  675. }
  676. return c.refs[key][ref], nil
  677. }
  678. type prAuthor struct {
  679. pr int
  680. author string
  681. }
  682. type prAuthorSet map[prAuthor]interface{}
  683. func (s prAuthorSet) has(item prAuthor) bool {
  684. _, contained := s[item]
  685. return contained
  686. }
  687. func newFakeTrustedChecker(key orgRepo) fakeTrustedChecker {
  688. return fakeTrustedChecker{
  689. errors: map[orgRepo]prAuthorSet{key: {}},
  690. trusted: map[orgRepo]map[prAuthor]bool{key: {}},
  691. }
  692. }
  693. type fakeTrustedChecker struct {
  694. errors map[orgRepo]prAuthorSet
  695. trusted map[orgRepo]map[prAuthor]bool
  696. }
  697. func (c *fakeTrustedChecker) trustedPullRequest(author, org, repo string, num int) (bool, error) {
  698. key := orgRepo{org: org, repo: repo}
  699. item := prAuthor{pr: num, author: author}
  700. if errs, exist := c.errors[key]; exist && errs.has(item) {
  701. return false, errors.New("failed to check trusted")
  702. }
  703. return c.trusted[key][item], nil
  704. }
  705. func TestControllerReconcile(t *testing.T) {
  706. // the diff from these configs causes:
  707. // - deletion (required-job),
  708. // - creation (new-required-job)
  709. // - migration (other-required-job)
  710. oldConfigData := `presubmits:
  711. "org/repo":
  712. - name: required-job
  713. context: required-job
  714. always_run: true
  715. - name: other-required-job
  716. context: other-required-job
  717. always_run: true`
  718. newConfigData := `presubmits:
  719. "org/repo":
  720. - name: other-required-job
  721. context: new-context
  722. always_run: true
  723. - name: new-required-job
  724. context: new-required-context
  725. always_run: true
  726. branches:
  727. - base`
  728. var oldConfig, newConfig config.Config
  729. if err := yaml.Unmarshal([]byte(oldConfigData), &oldConfig); err != nil {
  730. t.Fatalf("could not unmarshal old config: %v", err)
  731. }
  732. for _, presubmits := range oldConfig.PresubmitsStatic {
  733. if err := config.SetPresubmitRegexes(presubmits); err != nil {
  734. t.Fatalf("could not set presubmit regexes for old config: %v", err)
  735. }
  736. }
  737. if err := yaml.Unmarshal([]byte(newConfigData), &newConfig); err != nil {
  738. t.Fatalf("could not unmarshal new config: %v", err)
  739. }
  740. for _, presubmits := range newConfig.PresubmitsStatic {
  741. if err := config.SetPresubmitRegexes(presubmits); err != nil {
  742. t.Fatalf("could not set presubmit regexes for new config: %v", err)
  743. }
  744. }
  745. delta := config.Delta{Before: oldConfig, After: newConfig}
  746. migrate := migration{from: "other-required-job", to: "new-context"}
  747. org, repo := "org", "repo"
  748. orgRepoKey := orgRepo{org: org, repo: repo}
  749. prNumber := 1
  750. secondPrNumber := 2
  751. thirdPrNumber := 3
  752. author := "user"
  753. prAuthorKey := prAuthor{author: author, pr: prNumber}
  754. secondPrAuthorKey := prAuthor{author: author, pr: secondPrNumber}
  755. thirdPrAuthorKey := prAuthor{author: author, pr: thirdPrNumber}
  756. prOrgRepoKey := prKey{org: org, repo: repo, num: prNumber}
  757. thirdPrOrgRepoKey := prKey{org: org, repo: repo, num: thirdPrNumber}
  758. baseRef := "base"
  759. otherBaseRef := "other"
  760. baseSha := "abc"
  761. notMergable := false
  762. pr := github.PullRequest{
  763. User: github.User{
  764. Login: author,
  765. },
  766. Number: prNumber,
  767. Base: github.PullRequestBranch{
  768. Repo: github.Repo{
  769. Owner: github.User{
  770. Login: org,
  771. },
  772. Name: repo,
  773. },
  774. Ref: baseRef,
  775. },
  776. Head: github.PullRequestBranch{
  777. SHA: "prsha",
  778. },
  779. }
  780. secondPr := github.PullRequest{
  781. User: github.User{
  782. Login: author,
  783. },
  784. Number: secondPrNumber,
  785. Base: github.PullRequestBranch{
  786. Repo: github.Repo{
  787. Owner: github.User{
  788. Login: org,
  789. },
  790. Name: repo,
  791. },
  792. Ref: baseRef,
  793. },
  794. Head: github.PullRequestBranch{
  795. SHA: "prsha2",
  796. },
  797. Mergable: &notMergable,
  798. }
  799. thirdPr := github.PullRequest{
  800. User: github.User{
  801. Login: author,
  802. },
  803. Number: thirdPrNumber,
  804. Base: github.PullRequestBranch{
  805. Repo: github.Repo{
  806. Owner: github.User{
  807. Login: org,
  808. },
  809. Name: repo,
  810. },
  811. Ref: otherBaseRef,
  812. },
  813. Head: github.PullRequestBranch{
  814. SHA: "prsha3",
  815. },
  816. }
  817. var testCases = []struct {
  818. name string
  819. // generator creates the controller and a func that checks
  820. // the internal state of the fakes in the controller
  821. generator func() (Controller, func(*testing.T))
  822. expectErr bool
  823. }{
  824. {
  825. name: "ignored org skips creation, still does retire and migrate",
  826. generator: func() (Controller, func(*testing.T)) {
  827. fpjt := newfakeProwJobTriggerer()
  828. fghc := newFakeGitHubClient(orgRepoKey)
  829. fghc.prs[orgRepoKey] = []github.PullRequest{pr}
  830. fghc.refs[orgRepoKey]["heads/"+pr.Base.Ref] = baseSha
  831. fsm := newFakeMigrator(orgRepoKey)
  832. ftc := newFakeTrustedChecker(orgRepoKey)
  833. ftc.trusted[orgRepoKey][prAuthorKey] = true
  834. controller := Controller{
  835. continueOnError: true,
  836. addedPresubmitBlacklist: sets.NewString("org"),
  837. prowJobTriggerer: &fpjt,
  838. githubClient: &fghc,
  839. statusMigrator: &fsm,
  840. trustedChecker: &ftc,
  841. }
  842. checker := func(t *testing.T) {
  843. checkTriggerer(t, fpjt, map[prKey]sets.String{})
  844. checkMigrator(t, fsm, map[orgRepo]sets.String{orgRepoKey: sets.NewString("required-job")}, map[orgRepo]migrationSet{orgRepoKey: {migrate: nil}})
  845. }
  846. return controller, checker
  847. },
  848. },
  849. {
  850. name: "ignored org/repo skips creation, still does retire and migrate",
  851. generator: func() (Controller, func(*testing.T)) {
  852. fpjt := newfakeProwJobTriggerer()
  853. fghc := newFakeGitHubClient(orgRepoKey)
  854. fghc.prs[orgRepoKey] = []github.PullRequest{pr}
  855. fghc.refs[orgRepoKey]["heads/"+pr.Base.Ref] = baseSha
  856. fsm := newFakeMigrator(orgRepoKey)
  857. ftc := newFakeTrustedChecker(orgRepoKey)
  858. ftc.trusted[orgRepoKey][prAuthorKey] = true
  859. controller := Controller{
  860. continueOnError: true,
  861. addedPresubmitBlacklist: sets.NewString("org/repo"),
  862. prowJobTriggerer: &fpjt,
  863. githubClient: &fghc,
  864. statusMigrator: &fsm,
  865. trustedChecker: &ftc,
  866. }
  867. checker := func(t *testing.T) {
  868. checkTriggerer(t, fpjt, map[prKey]sets.String{})
  869. checkMigrator(t, fsm, map[orgRepo]sets.String{orgRepoKey: sets.NewString("required-job")}, map[orgRepo]migrationSet{orgRepoKey: {migrate: nil}})
  870. }
  871. return controller, checker
  872. },
  873. },
  874. {
  875. name: "no errors and trusted PR means we should see a trigger, retire and migrate",
  876. generator: func() (Controller, func(*testing.T)) {
  877. fpjt := newfakeProwJobTriggerer()
  878. fghc := newFakeGitHubClient(orgRepoKey)
  879. fghc.prs[orgRepoKey] = []github.PullRequest{pr}
  880. fghc.refs[orgRepoKey]["heads/"+pr.Base.Ref] = baseSha
  881. fsm := newFakeMigrator(orgRepoKey)
  882. ftc := newFakeTrustedChecker(orgRepoKey)
  883. ftc.trusted[orgRepoKey][prAuthorKey] = true
  884. controller := Controller{
  885. continueOnError: true,
  886. addedPresubmitBlacklist: sets.NewString(),
  887. prowJobTriggerer: &fpjt,
  888. githubClient: &fghc,
  889. statusMigrator: &fsm,
  890. trustedChecker: &ftc,
  891. }
  892. checker := func(t *testing.T) {
  893. expectedProwJob := map[prKey]sets.String{prOrgRepoKey: sets.NewString("new-required-job")}
  894. checkTriggerer(t, fpjt, expectedProwJob)
  895. checkMigrator(t, fsm, map[orgRepo]sets.String{orgRepoKey: sets.NewString("required-job")}, map[orgRepo]migrationSet{orgRepoKey: {migrate: nil}})
  896. }
  897. return controller, checker
  898. },
  899. },
  900. {
  901. name: "no errors and untrusted PR means we should see no trigger, a retire and a migrate",
  902. generator: func() (Controller, func(*testing.T)) {
  903. fpjt := newfakeProwJobTriggerer()
  904. fghc := newFakeGitHubClient(orgRepoKey)
  905. fghc.prs[orgRepoKey] = []github.PullRequest{pr}
  906. fghc.refs[orgRepoKey]["heads/"+pr.Base.Ref] = baseSha
  907. fsm := newFakeMigrator(orgRepoKey)
  908. ftc := newFakeTrustedChecker(orgRepoKey)
  909. ftc.trusted[orgRepoKey][prAuthorKey] = false
  910. controller := Controller{
  911. continueOnError: true,
  912. addedPresubmitBlacklist: sets.NewString(),
  913. prowJobTriggerer: &fpjt,
  914. githubClient: &fghc,
  915. statusMigrator: &fsm,
  916. trustedChecker: &ftc,
  917. }
  918. checker := func(t *testing.T) {
  919. checkTriggerer(t, fpjt, map[prKey]sets.String{})
  920. checkMigrator(t, fsm, map[orgRepo]sets.String{orgRepoKey: sets.NewString("required-job")}, map[orgRepo]migrationSet{orgRepoKey: {migrate: nil}})
  921. }
  922. return controller, checker
  923. },
  924. },
  925. {
  926. name: "no errors and unmergable PR means we should see no trigger, a retire and a migrate",
  927. generator: func() (Controller, func(*testing.T)) {
  928. fpjt := newfakeProwJobTriggerer()
  929. fghc := newFakeGitHubClient(orgRepoKey)
  930. fghc.prs[orgRepoKey] = []github.PullRequest{secondPr}
  931. fghc.refs[orgRepoKey]["heads/"+secondPr.Base.Ref] = baseSha
  932. fsm := newFakeMigrator(orgRepoKey)
  933. ftc := newFakeTrustedChecker(orgRepoKey)
  934. ftc.trusted[orgRepoKey][secondPrAuthorKey] = true
  935. controller := Controller{
  936. continueOnError: true,
  937. addedPresubmitBlacklist: sets.NewString(),
  938. prowJobTriggerer: &fpjt,
  939. githubClient: &fghc,
  940. statusMigrator: &fsm,
  941. trustedChecker: &ftc,
  942. }
  943. checker := func(t *testing.T) {
  944. checkTriggerer(t, fpjt, map[prKey]sets.String{})
  945. checkMigrator(t, fsm, map[orgRepo]sets.String{orgRepoKey: sets.NewString("required-job")}, map[orgRepo]migrationSet{orgRepoKey: {migrate: nil}})
  946. }
  947. return controller, checker
  948. },
  949. },
  950. {
  951. name: "no errors and PR that doesn't match the added job means we should see no trigger, a retire and a migrate",
  952. generator: func() (Controller, func(*testing.T)) {
  953. fpjt := newfakeProwJobTriggerer()
  954. fghc := newFakeGitHubClient(orgRepoKey)
  955. fghc.prs[orgRepoKey] = []github.PullRequest{thirdPr}
  956. fghc.refs[orgRepoKey]["heads/"+thirdPr.Base.Ref] = baseSha
  957. fsm := newFakeMigrator(orgRepoKey)
  958. ftc := newFakeTrustedChecker(orgRepoKey)
  959. ftc.trusted[orgRepoKey][thirdPrAuthorKey] = true
  960. controller := Controller{
  961. continueOnError: true,
  962. addedPresubmitBlacklist: sets.NewString(),
  963. prowJobTriggerer: &fpjt,
  964. githubClient: &fghc,
  965. statusMigrator: &fsm,
  966. trustedChecker: &ftc,
  967. }
  968. checker := func(t *testing.T) {
  969. checkTriggerer(t, fpjt, map[prKey]sets.String{thirdPrOrgRepoKey: sets.NewString()})
  970. checkMigrator(t, fsm, map[orgRepo]sets.String{orgRepoKey: sets.NewString("required-job")}, map[orgRepo]migrationSet{orgRepoKey: {migrate: nil}})
  971. }
  972. return controller, checker
  973. },
  974. },
  975. {
  976. name: "trust check error means we should see no trigger, a retire and a migrate",
  977. generator: func() (Controller, func(*testing.T)) {
  978. fpjt := newfakeProwJobTriggerer()
  979. fghc := newFakeGitHubClient(orgRepoKey)
  980. fghc.prs[orgRepoKey] = []github.PullRequest{pr}
  981. fghc.refs[orgRepoKey]["heads/"+pr.Base.Ref] = baseSha
  982. fsm := newFakeMigrator(orgRepoKey)
  983. ftc := newFakeTrustedChecker(orgRepoKey)
  984. ftc.errors = map[orgRepo]prAuthorSet{orgRepoKey: {prAuthorKey: nil}}
  985. controller := Controller{
  986. continueOnError: true,
  987. addedPresubmitBlacklist: sets.NewString(),
  988. prowJobTriggerer: &fpjt,
  989. githubClient: &fghc,
  990. statusMigrator: &fsm,
  991. trustedChecker: &ftc,
  992. }
  993. checker := func(t *testing.T) {
  994. checkTriggerer(t, fpjt, map[prKey]sets.String{})
  995. checkMigrator(t, fsm, map[orgRepo]sets.String{orgRepoKey: sets.NewString("required-job")}, map[orgRepo]migrationSet{orgRepoKey: {migrate: nil}})
  996. }
  997. return controller, checker
  998. },
  999. expectErr: true,
  1000. },
  1001. {
  1002. name: "trigger error means we should see no trigger, a retire and a migrate",
  1003. generator: func() (Controller, func(*testing.T)) {
  1004. fpjt := newfakeProwJobTriggerer()
  1005. fpjt.errors[prOrgRepoKey] = sets.NewString("new-required-job")
  1006. fghc := newFakeGitHubClient(orgRepoKey)
  1007. fghc.prs[orgRepoKey] = []github.PullRequest{pr}
  1008. fghc.refs[orgRepoKey]["heads/"+pr.Base.Ref] = baseSha
  1009. fsm := newFakeMigrator(orgRepoKey)
  1010. ftc := newFakeTrustedChecker(orgRepoKey)
  1011. ftc.errors = map[orgRepo]prAuthorSet{orgRepoKey: {prAuthorKey: nil}}
  1012. controller := Controller{
  1013. continueOnError: true,
  1014. addedPresubmitBlacklist: sets.NewString(),
  1015. prowJobTriggerer: &fpjt,
  1016. githubClient: &fghc,
  1017. statusMigrator: &fsm,
  1018. trustedChecker: &ftc,
  1019. }
  1020. checker := func(t *testing.T) {
  1021. checkTriggerer(t, fpjt, map[prKey]sets.String{})
  1022. checkMigrator(t, fsm, map[orgRepo]sets.String{orgRepoKey: sets.NewString("required-job")}, map[orgRepo]migrationSet{orgRepoKey: {migrate: nil}})
  1023. }
  1024. return controller, checker
  1025. },
  1026. expectErr: true,
  1027. },
  1028. {
  1029. name: "retire errors and trusted PR means we should see a trigger and migrate",
  1030. generator: func() (Controller, func(*testing.T)) {
  1031. fpjt := newfakeProwJobTriggerer()
  1032. fghc := newFakeGitHubClient(orgRepoKey)
  1033. fghc.prs[orgRepoKey] = []github.PullRequest{pr}
  1034. fghc.refs[orgRepoKey]["heads/"+pr.Base.Ref] = baseSha
  1035. fsm := newFakeMigrator(orgRepoKey)
  1036. fsm.retireErrors = map[orgRepo]sets.String{orgRepoKey: sets.NewString("required-job")}
  1037. ftc := newFakeTrustedChecker(orgRepoKey)
  1038. ftc.trusted[orgRepoKey][prAuthorKey] = true
  1039. controller := Controller{
  1040. continueOnError: true,
  1041. addedPresubmitBlacklist: sets.NewString(),
  1042. prowJobTriggerer: &fpjt,
  1043. githubClient: &fghc,
  1044. statusMigrator: &fsm,
  1045. trustedChecker: &ftc,
  1046. }
  1047. checker := func(t *testing.T) {
  1048. expectedProwJob := map[prKey]sets.String{prOrgRepoKey: sets.NewString("new-required-job")}
  1049. checkTriggerer(t, fpjt, expectedProwJob)
  1050. checkMigrator(t, fsm, map[orgRepo]sets.String{orgRepoKey: sets.NewString()}, map[orgRepo]migrationSet{orgRepoKey: {migrate: nil}})
  1051. }
  1052. return controller, checker
  1053. },
  1054. expectErr: true,
  1055. },
  1056. {
  1057. name: "migrate errors and trusted PR means we should see a trigger and retire",
  1058. generator: func() (Controller, func(*testing.T)) {
  1059. fpjt := newfakeProwJobTriggerer()
  1060. fghc := newFakeGitHubClient(orgRepoKey)
  1061. fghc.prs[orgRepoKey] = []github.PullRequest{pr}
  1062. fghc.refs[orgRepoKey]["heads/"+pr.Base.Ref] = baseSha
  1063. fsm := newFakeMigrator(orgRepoKey)
  1064. fsm.migrateErrors = map[orgRepo]migrationSet{orgRepoKey: {migrate: nil}}
  1065. ftc := newFakeTrustedChecker(orgRepoKey)
  1066. ftc.trusted[orgRepoKey][prAuthorKey] = true
  1067. controller := Controller{
  1068. continueOnError: true,
  1069. addedPresubmitBlacklist: sets.NewString(),
  1070. prowJobTriggerer: &fpjt,
  1071. githubClient: &fghc,
  1072. statusMigrator: &fsm,
  1073. trustedChecker: &ftc,
  1074. }
  1075. checker := func(t *testing.T) {
  1076. expectedProwJob := map[prKey]sets.String{prOrgRepoKey: sets.NewString("new-required-job")}
  1077. checkTriggerer(t, fpjt, expectedProwJob)
  1078. checkMigrator(t, fsm, map[orgRepo]sets.String{orgRepoKey: sets.NewString("required-job")}, map[orgRepo]migrationSet{orgRepoKey: {}})
  1079. }
  1080. return controller, checker
  1081. },
  1082. expectErr: true,
  1083. },
  1084. }
  1085. for _, testCase := range testCases {
  1086. t.Run(testCase.name, func(t *testing.T) {
  1087. controller, check := testCase.generator()
  1088. err := controller.reconcile(delta)
  1089. if err == nil && testCase.expectErr {
  1090. t.Errorf("expected an error, but got none")
  1091. }
  1092. if err != nil && !testCase.expectErr {
  1093. t.Errorf("expected no error, but got one: %v", err)
  1094. }
  1095. check(t)
  1096. })
  1097. }
  1098. }
  1099. func checkTriggerer(t *testing.T, triggerer fakeProwJobTriggerer, expectedCreatedJobs map[prKey]sets.String) {
  1100. if actual, expected := triggerer.created, expectedCreatedJobs; !reflect.DeepEqual(actual, expected) {
  1101. t.Errorf("did not create expected ProwJob: %s", diff.ObjectReflectDiff(actual, expected))
  1102. }
  1103. }
  1104. func checkMigrator(t *testing.T, migrator fakeMigrator, expectedRetiredStatuses map[orgRepo]sets.String, expectedMigratedStatuses map[orgRepo]migrationSet) {
  1105. if actual, expected := migrator.retired, expectedRetiredStatuses; !reflect.DeepEqual(actual, expected) {
  1106. t.Errorf("did not retire correct statuses: %s", diff.ObjectReflectDiff(actual, expected))
  1107. }
  1108. if actual, expected := migrator.migrated, expectedMigratedStatuses; !reflect.DeepEqual(actual, expected) {
  1109. t.Errorf("did not migrate correct statuses: %s", diff.ObjectReflectDiff(actual, expected))
  1110. }
  1111. }