@@ -35,10 +35,13 @@ import (
3535 "testing"
3636 "time"
3737
38+ "github.com/google/go-cmp/cmp"
39+ "github.com/google/go-cmp/cmp/cmpopts"
3840 coreapi "k8s.io/api/core/v1"
3941 "k8s.io/apimachinery/pkg/api/resource"
4042 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
4143 "k8s.io/apimachinery/pkg/util/sets"
44+ yaml "sigs.k8s.io/yaml/goyaml.v3"
4245
4346 prowapi "sigs.k8s.io/prow/pkg/apis/prowjobs/v1"
4447 cfg "sigs.k8s.io/prow/pkg/config"
@@ -1393,3 +1396,174 @@ func TestKubernetesProwJobsShouldNotUseDeprecatedScenarios(t *testing.T) {
13931396 t .Logf ("summary: %v/%v jobs using deprecated scenarios" , jobsToFix , len (jobs ))
13941397 }
13951398}
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