Merge pull request #138273 from pohly/unit-test-ownership

improve JUnit test names in integration and unit test jobs
This commit is contained in:
Kubernetes Prow Robot 2026-04-24 17:46:53 +05:30 committed by GitHub
commit 3610351abb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
49 changed files with 298 additions and 197 deletions

View file

@ -17,23 +17,33 @@ limitations under the License.
package main
import (
"bufio"
"bytes"
"encoding/xml"
"flag"
"fmt"
"io"
"os"
"os/exec"
"path"
"regexp"
"strings"
"k8s.io/kubernetes/cmd/prune-junit-xml/logparse"
"k8s.io/kubernetes/third_party/forked/gotestsum/junitxml"
"sigs.k8s.io/yaml"
)
func main() {
maxTextSize := flag.Int("max-text-size", 1, "maximum size of attribute or text (in MB)")
pruneTests := flag.Bool("prune-tests", true,
"prune's xml files to display only top level tests and failed sub-tests")
addOwners := flag.Bool("add-owners", true,
"when pruning tests, also look for OWNERs files of the packages and prefix the names with [sig-...] if found")
flag.Parse()
pkgs := newPackageOwners(*addOwners)
for _, path := range flag.Args() {
fmt.Printf("processing junit xml file : %s\n", path)
xmlReader, err := os.Open(path)
@ -48,7 +58,7 @@ func main() {
pruneXML(suites, *maxTextSize*1e6) // convert MB into bytes (roughly!)
if *pruneTests {
pruneTESTS(suites)
pruneTESTS(suites, pkgs)
}
xmlWriter, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
@ -121,7 +131,7 @@ func pruneStringIfNeeded(str *string, maxBytes int, msg string, args ...any) {
// This function condenses the junit xml to have package name as top level identifier
// and nesting under that.
func pruneTESTS(suites *junitxml.JUnitTestSuites) {
func pruneTESTS(suites *junitxml.JUnitTestSuites, pkgs *packageOwners) {
var updatedTestsuites []junitxml.JUnitTestSuite
for _, suite := range suites.Suites {
@ -130,10 +140,23 @@ func pruneTESTS(suites *junitxml.JUnitTestSuites) {
var updatedTestcaseFailure junitxml.JUnitFailure
failflag := false
name := suite.Name
// Inject the owning SIG prefix, if possible.
// This has to be done while we still have what
// is likely to be the full package name.
name = pkgs.addOwner(name)
regex := regexp.MustCompile(`^(.*?)/([^/]+)/?$`)
match := regex.FindStringSubmatch(name)
updatedTestcase.Classname = match[1]
updatedTestcase.Name = match[2]
baseName := match[1]
leafName := match[2]
// testgrid uses suite.Name.
// Spyglass/Prow use testcase.Classname.
// Therefore we need to update both.
suite.Name = baseName
updatedTestcase.Classname = baseName
updatedTestcase.Name = leafName
updatedTestcase.Time = suite.Time
updatedSystemOut := ""
updatedSystemErr := ""
@ -208,3 +231,79 @@ func streamXML(writer io.Writer, in *junitxml.JUnitTestSuites) error {
}
return encoder.Flush()
}
type packageOwners struct {
// pkgs maps from import path to directory.
pkgs map[string]string
}
func newPackageOwners(enabled bool) *packageOwners {
if !enabled {
return nil
}
return &packageOwners{
pkgs: make(map[string]string),
}
}
// addOwner takes a package name (= import path, like k8s.io/client-go),
// tries to look up the source code of the package, and then
// walks up until it finds an OWNERS file with some sig label.
// The first sig label found this way is used.
//
// If successful, that SIG gets added to the name.
// If not, the name remains unchanged.
func (p *packageOwners) addOwner(name string) string {
if p == nil {
return name
}
dir := p.pkgs[name]
if dir == "" {
// Look up via `go list`. To invoke it less often,
// we also ask for sub-packages and cache the results.
// We don't care about errors.
//
// This is roughly what golang.org/x/tools/go/packages does,
// which we can't use here because it would add a new
// dependency to k/k.
cmd := exec.Command("go", "list", "-f", "{{.ImportPath}}:{{.Dir}}", name+"/...")
out, _ := cmd.Output()
scanner := bufio.NewScanner(bytes.NewReader(out))
for scanner.Scan() {
line := scanner.Text()
parts := strings.SplitN(line, ":", 2)
if len(parts) != 2 {
continue
}
p.pkgs[parts[0]] = parts[1]
}
dir = p.pkgs[name]
}
// Walk up starting from an absolute path until we cannot go up further.
for ; dir != "" && dir != "." && dir != "/"; dir = path.Dir(dir) {
data, err := os.ReadFile(path.Join(dir, "OWNERS"))
if err != nil {
continue
}
var owners owners
if err := yaml.Unmarshal(data, &owners); err != nil {
continue
}
for _, label := range owners.Labels {
if strings.HasPrefix(label, "sig/") {
// Bingo!
return fmt.Sprintf("[sig-%s] %s", label[4:], name)
}
}
}
return name
}
// owners contains only fields from https://go.k8s.io/owners that we care about.
type owners struct {
Labels []string `json:"labels"`
}

View file

@ -76,16 +76,16 @@ func TestPruneXML(t *testing.T) {
func TestPruneTESTS(t *testing.T) {
sourceXML := `<?xml version="1.0" encoding="UTF-8"?>
<testsuites>
<testsuite tests="6" failures="0" time="5.50000" name="k8s.io/kubernetes/cluster/gce/cos" timestamp="">
<testsuite tests="6" failures="0" time="5.50000" name="k8s.io/kubernetes/cluster/gce/gci" timestamp="">
<properties>
<property name="go.version" value="go1.18 linux/amd64"></property>
</properties>
<testcase classname="k8s.io/kubernetes/cluster/gce/cos" name="TestServerOverride/ETCD-SERVERS_is_not_set_-_default_override" time="0.950000"></testcase>
<testcase classname="k8s.io/kubernetes/cluster/gce/cos" name="TestServerOverride/ETCD-SERVERS_and_ETCD_SERVERS_OVERRIDES_are_set" time="0.660000"></testcase>
<testcase classname="k8s.io/kubernetes/cluster/gce/cos" name="TestServerOverride" time="1.610000"></testcase>
<testcase classname="k8s.io/kubernetes/cluster/gce/cos" name="TestStorageOptions/storage_options_are_supplied" time="0.860000"></testcase>
<testcase classname="k8s.io/kubernetes/cluster/gce/cos" name="TestStorageOptions/storage_options_are_not_supplied" time="0.280000"></testcase>
<testcase classname="k8s.io/kubernetes/cluster/gce/cos" name="TestStorageOptions" time="1.140000"></testcase>
<testcase classname="k8s.io/kubernetes/cluster/gce/gci" name="TestServerOverride/ETCD-SERVERS_is_not_set_-_default_override" time="0.950000"></testcase>
<testcase classname="k8s.io/kubernetes/cluster/gce/gci" name="TestServerOverride/ETCD-SERVERS_and_ETCD_SERVERS_OVERRIDES_are_set" time="0.660000"></testcase>
<testcase classname="k8s.io/kubernetes/cluster/gce/gci" name="TestServerOverride" time="1.610000"></testcase>
<testcase classname="k8s.io/kubernetes/cluster/gce/gci" name="TestStorageOptions/storage_options_are_supplied" time="0.860000"></testcase>
<testcase classname="k8s.io/kubernetes/cluster/gce/gci" name="TestStorageOptions/storage_options_are_not_supplied" time="0.280000"></testcase>
<testcase classname="k8s.io/kubernetes/cluster/gce/gci" name="TestStorageOptions" time="1.140000"></testcase>
</testsuite>
<testsuite tests="2" failures="1" time="30.050000" name="k8s.io/kubernetes/test/integration/apimachinery" timestamp="">
<properties>
@ -129,25 +129,34 @@ func TestPruneTESTS(t *testing.T) {
<failure message="Failed" type="">sub-test failed</failure>
</testcase>
</testsuite>
<testsuite tests="1" failures="0" time="30.050000" name="k8s.io/client-go/tools/cache" timestamp="">
<properties></properties>
<testcase classname="k8s.io/client-go/tools/cache" name="foobar" time="30.050000"></testcase>
</testsuite>
</testsuites>`
// This test uses the real OWNERS files from k/k because those exist.
// The downside it that OWNERS changes (not unlikely in the case of cluster/gce)
// imply changing this test data.
//
// Fake packages have no source and thus no OWNERS.
outputXML := `<?xml version="1.0" encoding="UTF-8"?>
<testsuites>
<testsuite tests="6" failures="0" time="5.50000" name="k8s.io/kubernetes/cluster/gce/cos" timestamp="">
<testsuite tests="6" failures="0" time="5.50000" name="[sig-cloud-provider] k8s.io/kubernetes/cluster/gce" timestamp="">
<properties>
<property name="go.version" value="go1.18 linux/amd64"></property>
</properties>
<testcase classname="k8s.io/kubernetes/cluster/gce" name="cos" time="5.50000"></testcase>
<testcase classname="[sig-cloud-provider] k8s.io/kubernetes/cluster/gce" name="gci" time="5.50000"></testcase>
</testsuite>
<testsuite tests="2" failures="1" time="30.050000" name="k8s.io/kubernetes/test/integration/apimachinery" timestamp="">
<testsuite tests="2" failures="1" time="30.050000" name="[sig-api-machinery] k8s.io/kubernetes/test/integration" timestamp="">
<properties>
<property name="go.version" value="go1.18 linux/amd64"></property>
</properties>
<testcase classname="k8s.io/kubernetes/test/integration" name="apimachinery" time="30.050000">
<testcase classname="[sig-api-machinery] k8s.io/kubernetes/test/integration" name="apimachinery" time="30.050000">
<failure message="Failed" type="">FailureContent</failure>
</testcase>
</testsuite>
<testsuite tests="3" failures="2" time="30.050000" name="k8s.io/kubernetes/test/integration/apimachinery2" timestamp="">
<testsuite tests="3" failures="2" time="30.050000" name="k8s.io/kubernetes/test/integration" timestamp="">
<properties>
<property name="go.version" value="go1.18 linux/amd64"></property>
</properties>
@ -155,7 +164,7 @@ func TestPruneTESTS(t *testing.T) {
<failure message="FailedA&#xA;&#xA;FailedB" type="">FailureContentA&#xA;&#xA;FailureContentB</failure>
</testcase>
</testsuite>
<testsuite tests="3" failures="3" time="40.050000" name="k8s.io/kubernetes/test/integration/apimachinery3" timestamp="">
<testsuite tests="3" failures="3" time="40.050000" name="k8s.io/kubernetes/test/integration" timestamp="">
<properties>
<property name="go.version" value="go1.18 linux/amd64"></property>
</properties>
@ -165,9 +174,13 @@ func TestPruneTESTS(t *testing.T) {
<system-err>err A&#xA;&#xA;err B</system-err>
</testcase>
</testsuite>
<testsuite tests="1" failures="0" time="30.050000" name="[sig-api-machinery] k8s.io/client-go/tools" timestamp="">
<properties></properties>
<testcase classname="[sig-api-machinery] k8s.io/client-go/tools" name="cache" time="30.050000"></testcase>
</testsuite>
</testsuites>`
suites, _ := fetchXML(strings.NewReader(sourceXML))
pruneTESTS(suites)
pruneTESTS(suites, newPackageOwners(true))
var output bytes.Buffer
writer := bufio.NewWriter(&output)
_ = streamXML(writer, suites)

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/api-machinery

View file

@ -3,3 +3,5 @@ approvers:
- alexzielenski
- jefftree
- jpbetz
labels:
- sig/api-machinery

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/auth

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/auth

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/auth

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/api-machinery

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/cloud-provider

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/auth

View file

@ -1,125 +0,0 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package configmap
// This file tests use of the configMap API resource.
import (
"context"
"testing"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientset "k8s.io/client-go/kubernetes"
kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
"k8s.io/kubernetes/test/integration"
"k8s.io/kubernetes/test/integration/framework"
)
// TestConfigMap tests apiserver-side behavior of creation of ConfigMaps and pods that consume them.
func TestConfigMap(t *testing.T) {
// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
server := kubeapiservertesting.StartTestServerOrDie(t, nil, framework.DefaultTestServerFlags(), framework.SharedEtcd())
defer server.TearDownFn()
client := clientset.NewForConfigOrDie(server.ClientConfig)
ns := framework.CreateNamespaceOrDie(client, "config-map", t)
defer framework.DeleteNamespaceOrDie(client, ns, t)
DoTestConfigMap(t, client, ns)
}
func DoTestConfigMap(t *testing.T, client clientset.Interface, ns *v1.Namespace) {
cfg := v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "configmap",
Namespace: ns.Name,
},
Data: map[string]string{
"data-1": "value-1",
"data-2": "value-2",
"data-3": "value-3",
},
}
if _, err := client.CoreV1().ConfigMaps(cfg.Namespace).Create(context.TODO(), &cfg, metav1.CreateOptions{}); err != nil {
t.Errorf("unable to create test configMap: %v", err)
}
defer deleteConfigMapOrErrorf(t, client, cfg.Namespace, cfg.Name)
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "XXX",
Namespace: ns.Name,
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "fake-name",
Image: "fakeimage",
Env: []v1.EnvVar{
{
Name: "CONFIG_DATA_1",
ValueFrom: &v1.EnvVarSource{
ConfigMapKeyRef: &v1.ConfigMapKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: "configmap",
},
Key: "data-1",
},
},
},
{
Name: "CONFIG_DATA_2",
ValueFrom: &v1.EnvVarSource{
ConfigMapKeyRef: &v1.ConfigMapKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: "configmap",
},
Key: "data-2",
},
},
}, {
Name: "CONFIG_DATA_3",
ValueFrom: &v1.EnvVarSource{
ConfigMapKeyRef: &v1.ConfigMapKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: "configmap",
},
Key: "data-3",
},
},
},
},
},
},
},
}
pod.ObjectMeta.Name = "uses-configmap"
if _, err := client.CoreV1().Pods(ns.Name).Create(context.TODO(), pod, metav1.CreateOptions{}); err != nil {
t.Errorf("Failed to create pod: %v", err)
}
defer integration.DeletePodOrErrorf(t, client, ns.Name, pod.Name)
}
func deleteConfigMapOrErrorf(t *testing.T, c clientset.Interface, ns, name string) {
if err := c.CoreV1().ConfigMaps(ns).Delete(context.TODO(), name, metav1.DeleteOptions{}); err != nil {
t.Errorf("unable to delete ConfigMap %v: %v", name, err)
}
}

View file

@ -1,27 +0,0 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package configmap
import (
"testing"
"k8s.io/kubernetes/test/integration/framework"
)
func TestMain(m *testing.M) {
framework.EtcdMain(m.Run)
}

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/apps

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/apps

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/apps

View file

@ -6,3 +6,5 @@ approvers:
reviewers:
- deads2k
- liggitt
labels:
- sig/api-machinery

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/network

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/network

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/network

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/instrumentation

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/apps

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/api-machinery

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/api-machinery

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/network

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/node

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/api-machinery

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/network

View file

@ -4,3 +4,5 @@ approvers:
- sig-node-approvers
reviewers:
- sig-node-reviewers
labels:
- sig/node

View file

@ -1,27 +0,0 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package objectmeta
import (
"testing"
"k8s.io/kubernetes/test/integration/framework"
)
func TestMain(m *testing.M) {
framework.EtcdMain(m.Run)
}

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/api-machinery

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/auth

View file

@ -10,3 +10,5 @@ reviewers:
emeritus_approvers:
- vishh
- dchen1107
labels:
- sig/node

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/storage

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/api-machinery

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/apps

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/apps

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/apps

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/auth

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/network

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/auth

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/network

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/auth

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/api-machinery

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/apps

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/storage

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/api-machinery

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/api-machinery

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/auth

View file

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- sig/apps