istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tests/binary/binaries_test.go (about) 1 // Copyright Istio Authors 2 // 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 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package binary 16 17 import ( 18 "encoding/json" 19 "flag" 20 "os" 21 "os/exec" 22 "path" 23 "strings" 24 "testing" 25 26 "istio.io/istio/pkg/util/sets" 27 "istio.io/istio/pkg/version" 28 ) 29 30 var ( 31 binaries *string 32 releasedir *string 33 ) 34 35 func TestMain(m *testing.M) { 36 releasedir = flag.String("base-dir", "", "directory for binaries") 37 binaries = flag.String("binaries", "", "space separated binaries to test") 38 flag.Parse() 39 os.Exit(m.Run()) 40 } 41 42 func TestVersion(t *testing.T) { 43 runBinariesTest(t, func(t *testing.T, name string) { 44 if nonGoBinaries.Contains(name) { 45 return 46 } 47 if nonVersionBinaries.Contains(name) { 48 return 49 } 50 cmd := path.Join(*releasedir, name) 51 args := []string{"version", "-ojson"} 52 if name == "istioctl" { 53 args = append(args, "--remote=false") 54 } 55 56 out, err := exec.Command(cmd, args...).Output() 57 if err != nil { 58 t.Fatalf("--version failed with error: %v. Output: %v", err, string(out)) 59 } 60 61 var resp version.Version 62 if err := json.Unmarshal(out, &resp); err != nil { 63 t.Fatalf("Failed to marshal to json: %v. Output: %v", err, string(out)) 64 } 65 66 verInfo := resp.ClientVersion 67 68 validateField(t, "Version", verInfo.Version) 69 validateField(t, "GitRevision", verInfo.GitRevision) 70 validateField(t, "GolangVersion", verInfo.GolangVersion) 71 validateField(t, "BuildStatus", verInfo.BuildStatus) 72 validateField(t, "GitTag", verInfo.GitTag) 73 }) 74 } 75 76 var ( 77 nonGoBinaries = sets.New("ztunnel", "envoy") 78 nonVersionBinaries = sets.New("client", "server") 79 ) 80 81 // Test that flags do not get polluted with unexpected flags 82 func TestFlags(t *testing.T) { 83 runBinariesTest(t, func(t *testing.T, name string) { 84 if nonGoBinaries.Contains(name) { 85 return 86 } 87 cmd := path.Join(*releasedir, name) 88 out, err := exec.Command(cmd, "--help").Output() 89 if err != nil { 90 t.Fatalf("--help failed with error: %v. Output: %v", err, string(out)) 91 } 92 93 for _, denylist := range denylistedFlags { 94 if strings.Contains(string(out), denylist) { 95 t.Fatalf("binary contains unexpected flags: %v", string(out)) 96 } 97 } 98 }) 99 } 100 101 // Test that binary sizes do not bloat 102 func TestBinarySizes(t *testing.T) { 103 cases := map[string]struct { 104 minMb int64 105 maxMb int64 106 }{ 107 // TODO: shrink the ranges here once the active work to reduce binary size is complete 108 // For now, having two small a range will result in lots of "merge conflicts" 109 "istioctl": {60, 85}, 110 "pilot-agent": {20, 28}, 111 // TODO(https://github.com/kubernetes/kubernetes/issues/101384) bump this down a bit? 112 "pilot-discovery": {60, 85}, 113 "bug-report": {60, 80}, 114 "client": {15, 30}, 115 "server": {15, 30}, 116 "envoy": {60, 130}, 117 "ztunnel": {10, 30}, 118 } 119 120 runBinariesTest(t, func(t *testing.T, name string) { 121 tt, f := cases[name] 122 if !f { 123 t.Fatalf("min/max binary size not specified for %v", name) 124 } 125 cmd := path.Join(*releasedir, name) 126 fi, err := os.Stat(cmd) 127 if err != nil { 128 t.Fatal(err) 129 } 130 got := fi.Size() / (1000 * 1000) 131 t.Logf("Actual size: %dmb. Range: [%dmb, %dmb]", got, tt.minMb, tt.maxMb) 132 if got > tt.maxMb { 133 t.Fatalf("Binary size of %dmb was greater than max allowed size %dmb", got, tt.maxMb) 134 } 135 if got < tt.minMb { 136 t.Fatalf("Binary size of %dmb was smaller than min allowed size %dmb. This is very likely a good thing, "+ 137 "indicating the binary size has decreased. The test will fail to ensure you update the test thresholds to ensure "+ 138 "the improvements are 'locked in'.", got, tt.minMb) 139 } 140 }) 141 } 142 143 // If this flag is present, it means "testing" was imported by code that is built by the binary 144 var denylistedFlags = []string{ 145 "--test.memprofilerate", 146 } 147 148 func runBinariesTest(t *testing.T, f func(t *testing.T, name string)) { 149 t.Helper() 150 if *releasedir == "" { 151 t.Skip("release dir not set") 152 } 153 binariesToTest := strings.Split(*binaries, " ") 154 if len(binariesToTest) == 0 { 155 t.Fatal("No binaries to test. Pass the --binaries flag.") 156 } 157 for _, b := range binariesToTest { 158 t.Run(b, func(t *testing.T) { 159 f(t, b) 160 }) 161 } 162 } 163 164 // TODO we may be able to do more validation of fields here, but because it changes based on the environment this is not easy 165 // For now ensuring the fields even get populated is most important. 166 func validateField(t *testing.T, field, s string) { 167 t.Helper() 168 if s == "unknown" || s == "" { 169 t.Errorf("Field %v was invalid. Got: %v.", field, s) 170 } 171 }