@@ -35,10 +35,13 @@ import (
35
35
"testing"
36
36
"time"
37
37
38
+ "github.com/google/go-cmp/cmp"
39
+ "github.com/google/go-cmp/cmp/cmpopts"
38
40
coreapi "k8s.io/api/core/v1"
39
41
"k8s.io/apimachinery/pkg/api/resource"
40
42
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
41
43
"k8s.io/apimachinery/pkg/util/sets"
44
+ yaml "sigs.k8s.io/yaml/goyaml.v3"
42
45
43
46
prowapi "sigs.k8s.io/prow/pkg/apis/prowjobs/v1"
44
47
cfg "sigs.k8s.io/prow/pkg/config"
@@ -1393,3 +1396,174 @@ func TestKubernetesProwJobsShouldNotUseDeprecatedScenarios(t *testing.T) {
1393
1396
t .Logf ("summary: %v/%v jobs using deprecated scenarios" , jobsToFix , len (jobs ))
1394
1397
}
1395
1398
}
1399
+
1400
+ func TestKubernetesPresubmitJobs (t * testing.T ) {
1401
+ jobs := c .AllStaticPresubmits ([]string {"kubernetes/kubernetes" })
1402
+ var expected presubmitJobs
1403
+
1404
+ for _ , job := range jobs {
1405
+ if ! job .AlwaysRun && job .RunIfChanged == "" {
1406
+ // Manually triggered, no additional review needed.
1407
+ continue
1408
+ }
1409
+
1410
+ // Mirror those attributes of the job which must trigger additional reviews
1411
+ // or are needed to identify the job.
1412
+ j := presubmitJob {
1413
+ Name : job .Name ,
1414
+ SkipBranches : job .SkipBranches ,
1415
+ Branches : job .Branches ,
1416
+
1417
+ Optional : job .Optional ,
1418
+ RunIfChanged : job .RunIfChanged ,
1419
+ SkipIfOnlyChanged : job .SkipIfOnlyChanged ,
1420
+ }
1421
+
1422
+ // This uses separate top-level fields instead of job attributes to
1423
+ // make it more obvious when run_if_changed is used.
1424
+ if job .AlwaysRun {
1425
+ expected .AlwaysRun = append (expected .AlwaysRun , j )
1426
+ } else {
1427
+ expected .RunIfChanged = append (expected .RunIfChanged , j )
1428
+ }
1429
+
1430
+ }
1431
+ expected .Normalize ()
1432
+
1433
+ // Encode the expected content.
1434
+ var expectedData bytes.Buffer
1435
+ encoder := yaml .NewEncoder (& expectedData )
1436
+ encoder .SetIndent (4 )
1437
+ if err := encoder .Encode (expected ); err != nil {
1438
+ t .Fatalf ("unexpected error encoding %s: %v" , presubmitsFile , err )
1439
+ }
1440
+
1441
+ // Compare. This proceeds on read or decoding errors because
1442
+ // the file might get re-generated below.
1443
+ var actual presubmitJobs
1444
+ actualData , err := os .ReadFile (presubmitsFile )
1445
+ if err != nil && ! os .IsNotExist (err ) {
1446
+ t .Errorf ("unexpected error: %v" , err )
1447
+ }
1448
+ if err := yaml .Unmarshal (actualData , & actual ); err != nil {
1449
+ t .Errorf ("unexpected error decoding %s: %v" , presubmitsFile , err )
1450
+ }
1451
+
1452
+ // First check the in-memory structs. The diff is nicer for them (more context).
1453
+ diff := cmp .Diff (actual , expected )
1454
+ if diff == "" {
1455
+ // Next check the encoded data. This should only be different on test updates.
1456
+ diff = cmp .Diff (string (actualData ), expectedData .String (), cmpopts .AcyclicTransformer ("SplitLines" , func (s string ) []string {
1457
+ return strings .Split (s , "\n " )
1458
+ }))
1459
+ }
1460
+
1461
+ if diff != "" {
1462
+ t .Errorf (`
1463
+ %s is out-dated. Detected differences (- actual, + expected):
1464
+ %s
1465
+
1466
+ Blocking pre-submit jobs must be for stable, important features.
1467
+ Non-blocking pre-submit jobs should only be run automatically if they meet
1468
+ the criteria outlined in https://github.com/kubernetes/community/pull/8196.
1469
+
1470
+ To ensure that this is considered when defining pre-submit jobs, they
1471
+ need to be listed in %s. If the pre-submit job is really needed,
1472
+ re-run the test with UPDATE_FIXTURE_DATA=true and include the modified
1473
+ file.
1474
+
1475
+
1476
+ ` , presubmitsFile , diff , presubmitsFile )
1477
+ if value , _ := os .LookupEnv ("UPDATE_FIXTURE_DATA" ); value == "true" {
1478
+ if err := os .WriteFile (presubmitsFile , expectedData .Bytes (), 0644 ); err != nil {
1479
+ t .Fatalf ("unexpected error: %v" , err )
1480
+ }
1481
+ }
1482
+ }
1483
+ }
1484
+
1485
+ // presubmitsFile contains the following struct.
1486
+ const presubmitsFile = "presubmit-jobs.yaml"
1487
+
1488
+ type presubmitJobs struct {
1489
+ AlwaysRun []presubmitJob `yaml:"always_run"`
1490
+ RunIfChanged []presubmitJob `yaml:"run_if_changed"`
1491
+ }
1492
+ type presubmitJob struct {
1493
+ Name string `yaml:"name"`
1494
+ SkipBranches []string `yaml:"skip_branches,omitempty"`
1495
+ Branches []string `yaml:"branches,omitempty"`
1496
+ Optional bool `yaml:"optional,omitempty"`
1497
+ RunIfChanged string `yaml:"run_if_changed,omitempty"`
1498
+ SkipIfOnlyChanged string `yaml:"skip_if_only_changed,omitempty"`
1499
+ }
1500
+
1501
+ func (p * presubmitJobs ) Normalize () {
1502
+ sortJobs (& p .AlwaysRun )
1503
+ sortJobs (& p .RunIfChanged )
1504
+ }
1505
+
1506
+ func sortJobs (jobs * []presubmitJob ) {
1507
+ for _ , job := range * jobs {
1508
+ sort .Strings (job .SkipBranches )
1509
+ sort .Strings (job .Branches )
1510
+ }
1511
+ sort .Slice (* jobs , func (i , j int ) bool {
1512
+ switch strings .Compare ((* jobs )[i ].Name , (* jobs )[j ].Name ) {
1513
+ case - 1 :
1514
+ return true
1515
+ case 1 :
1516
+ return false
1517
+ }
1518
+ switch slices .Compare ((* jobs )[i ].SkipBranches , (* jobs )[j ].SkipBranches ) {
1519
+ case - 1 :
1520
+ return true
1521
+ case 1 :
1522
+ return false
1523
+ }
1524
+ switch slices .Compare ((* jobs )[i ].Branches , (* jobs )[j ].Branches ) {
1525
+ case - 1 :
1526
+ return true
1527
+ case 1 :
1528
+ return false
1529
+ }
1530
+ return false
1531
+ })
1532
+
1533
+ // If a job has the same settings regardless of the branch, then
1534
+ // we can reduce to a single entry without the branch info.
1535
+ shorterJobs := make ([]presubmitJob , 0 , len (* jobs ))
1536
+ for i := 0 ; i < len (* jobs ); {
1537
+ job := (* jobs )[i ]
1538
+ job .Branches = nil
1539
+ job .SkipBranches = nil
1540
+
1541
+ if sameSettings (* jobs , job ) {
1542
+ shorterJobs = append (shorterJobs , job )
1543
+ // Fast-forward to next job.
1544
+ for i < len (* jobs ) && (* jobs )[i ].Name == job .Name {
1545
+ i ++
1546
+ }
1547
+ } else {
1548
+ // Keep all of the different entries.
1549
+ for i < len (* jobs ) && (* jobs )[i ].Name == job .Name {
1550
+ shorterJobs = append (shorterJobs , (* jobs )[i ])
1551
+ }
1552
+ }
1553
+ }
1554
+ * jobs = shorterJobs
1555
+ }
1556
+
1557
+ func sameSettings (jobs []presubmitJob , ref presubmitJob ) bool {
1558
+ for _ , job := range jobs {
1559
+ if job .Name != ref .Name {
1560
+ continue
1561
+ }
1562
+ if job .Optional != ref .Optional ||
1563
+ job .RunIfChanged != ref .RunIfChanged ||
1564
+ job .SkipIfOnlyChanged != ref .SkipIfOnlyChanged {
1565
+ return false
1566
+ }
1567
+ }
1568
+ return true
1569
+ }
0 commit comments