github.com/google/fleetspeak@v0.1.15-0.20240426164851-4f31f62c1aea/fleetspeak/src/client/daemonservice/execution/execution_test.go (about) 1 // Copyright 2017 Google Inc. 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 // https://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 execution 16 17 import ( 18 "bytes" 19 "context" 20 "os" 21 "strings" 22 "testing" 23 "time" 24 25 "github.com/google/fleetspeak/fleetspeak/src/client/channel" 26 "github.com/google/fleetspeak/fleetspeak/src/client/clitesting" 27 "github.com/google/fleetspeak/fleetspeak/src/client/service" 28 "google.golang.org/protobuf/proto" 29 30 dspb "github.com/google/fleetspeak/fleetspeak/src/client/daemonservice/proto/fleetspeak_daemonservice" 31 fspb "github.com/google/fleetspeak/fleetspeak/src/common/proto/fleetspeak" 32 mpb "github.com/google/fleetspeak/fleetspeak/src/common/proto/fleetspeak_monitoring" 33 ) 34 35 func patchDuration(t *testing.T, d *time.Duration, value time.Duration) { 36 t.Helper() 37 prev := *d 38 *d = value 39 t.Cleanup(func() { *d = prev }) 40 } 41 42 func TestFailures(t *testing.T) { 43 patchDuration(t, &channel.MagicTimeout, 50*time.Millisecond) 44 patchDuration(t, &channel.MessageTimeout, 50*time.Millisecond) 45 patchDuration(t, &startupDataTimeout, time.Second) 46 47 sc := clitesting.MockServiceContext{ 48 OutChan: make(chan *fspb.Message, 5), 49 } 50 if _, err := os.Stat(testClient(t)); err != nil { 51 t.Fatalf("can't stat testclient binary [%v]: %v", testClient(t), err) 52 } 53 54 // These misbehaviors should fail after MagicTimeout, or sooner. 55 for _, mode := range []string{"freeze", "freezeHard", "garbage", "die"} { 56 t.Run(mode, func(t *testing.T) { 57 dsc := &dspb.Config{ 58 Argv: []string{testClient(t), "--mode=" + mode}, 59 } 60 61 ex, err := New(context.Background(), "TestService", dsc, &sc) 62 if err != nil { 63 t.Fatalf("execution.New returned error: %v", err) 64 } 65 defer ex.Shutdown() 66 67 waitRes := make(chan error) 68 go func() { 69 waitRes <- ex.Wait() 70 }() 71 select { 72 case err := <-waitRes: 73 if err == nil { 74 t.Errorf("expected error from process, got nil") 75 } 76 case <-time.After(10 * time.Second): 77 t.Errorf("process should have been canceled, but kept running") 78 } 79 }) 80 } 81 } 82 83 func TestLoopback(t *testing.T) { 84 sc := clitesting.MockServiceContext{ 85 OutChan: make(chan *fspb.Message), 86 } 87 dsc := &dspb.Config{ 88 Argv: []string{testClient(t), "--mode=loopback"}, 89 } 90 ex := mustNew(t, "TestService", dsc, &sc) 91 msgs := []*fspb.Message{ 92 { 93 MessageId: []byte("\000\000\000"), 94 MessageType: "RequestTypeA", 95 }, 96 { 97 MessageId: []byte("\000\000\001"), 98 MessageType: "RequestTypeB", 99 }, 100 } 101 go func() { 102 for _, m := range msgs { 103 err := ex.SendMsg(context.Background(), m) 104 if err != nil { 105 t.Errorf("ex.SendMsg: %v", err) 106 } 107 } 108 }() 109 for _, m := range msgs { 110 got := <-sc.OutChan 111 want := proto.Clone(m).(*fspb.Message) 112 want.MessageType = want.MessageType + "Response" 113 if !proto.Equal(got, want) { 114 t.Errorf("Unexpected message from loopback: got [%v], want [%v]", got, want) 115 } 116 } 117 } 118 119 func TestStd(t *testing.T) { 120 sc := clitesting.MockServiceContext{ 121 OutChan: make(chan *fspb.Message, 20), 122 } 123 dsc := &dspb.Config{ 124 Argv: []string{testClient(t), "--mode=stdSpam"}, 125 StdParams: &dspb.Config_StdParams{ 126 ServiceName: "TestService", 127 FlushTimeSeconds: 5, 128 }, 129 } 130 ex := mustNew(t, "TestService", dsc, &sc) 131 _ = ex // only indirectly used through input and output 132 133 wantIn := []byte(strings.Repeat("The quick brown fox jumped over the lazy dogs.\n", 128*1024)) 134 wantErr := []byte(strings.Repeat("The brown quick fox jumped over some lazy dogs.\n", 128*1024)) 135 136 var bufIn bytes.Buffer 137 bufIn.Grow(len(wantIn)) 138 var bufErr bytes.Buffer 139 bufErr.Grow(len(wantErr)) 140 141 start := time.Now() 142 for bufIn.Len() < len(wantIn) || bufErr.Len() < len(wantErr) { 143 m := <-sc.OutChan 144 if m.MessageType == "ResourceUsage" { 145 continue 146 } 147 if m.MessageType != "StdOutput" { 148 t.Errorf("Received unexpected message type: %s", m.MessageType) 149 continue 150 } 151 od := &dspb.StdOutputData{} 152 if err := m.Data.UnmarshalTo(od); err != nil { 153 t.Fatalf("Unable to unmarshal StdOutputData: %v", err) 154 } 155 bufIn.Write(od.Stdout) 156 bufErr.Write(od.Stderr) 157 } 158 159 // Flushes should happen when the buffer is full. The last flush might take up 160 // to 5 seconds occur, the rest should occur quickly. 161 runtime := time.Since(start) 162 deadline := 10 * time.Second 163 if runtime > deadline { 164 t.Errorf("took %v to receive std data, should be less than %v", runtime, deadline) 165 } 166 167 if !bytes.Equal(bufIn.Bytes(), wantIn) { 168 t.Error("received stdout does not match expected") 169 } 170 if !bytes.Equal(bufErr.Bytes(), wantErr) { 171 t.Error("received stderr does not match expected") 172 } 173 } 174 175 func TestStats(t *testing.T) { 176 sc := clitesting.MockServiceContext{ 177 OutChan: make(chan *fspb.Message, 2000), 178 } 179 dsc := &dspb.Config{ 180 Argv: []string{testClient(t), "--mode=loopback"}, 181 ResourceMonitoringSampleSize: 2, 182 ResourceMonitoringSamplePeriodSeconds: 1, 183 } 184 ex := mustNew(t, "TestService", dsc, &sc) 185 186 // Run TestService for 4.2 seconds. We expect a resource-usage 187 // report to be sent every 2000 milliseconds (samplePeriod * sampleSize). 188 done := make(chan struct{}) 189 go func() { 190 for i := range 42 { 191 time.Sleep(100 * time.Millisecond) 192 m := &fspb.Message{ 193 MessageId: []byte{byte(i)}, 194 MessageType: "DummyMessage", 195 } 196 err := ex.SendMsg(context.TODO(), m) 197 if err != nil { 198 t.Errorf("ex.SendMsg: %v", err) 199 } 200 } 201 close(done) 202 }() 203 204 ruCnt := 0 205 finalRUReceived := false 206 207 for !finalRUReceived { 208 select { 209 case <-done: 210 return 211 case m := <-sc.OutChan: 212 if m.MessageType == "DummyMessageResponse" { 213 continue 214 } 215 if m.MessageType != "ResourceUsage" { 216 t.Errorf("Received unexpected message type: %s", m.MessageType) 217 continue 218 } 219 rud := &mpb.ResourceUsageData{} 220 if err := m.Data.UnmarshalTo(rud); err != nil { 221 t.Fatalf("Unable to unmarshal ResourceUsageData: %v", err) 222 } 223 ruCnt++ 224 if rud.ProcessTerminated { 225 finalRUReceived = true 226 continue 227 } 228 ru := rud.ResourceUsage 229 if ru == nil { 230 t.Error("ResourceUsageData should have non-nil ResourceUsage") 231 continue 232 } 233 if ru.MeanResidentMemory <= 0.0 { 234 t.Errorf("ResourceUsage.MeanResidentMemory should be >0, got: %.2f", ru.MeanResidentMemory) 235 continue 236 } 237 } 238 } 239 240 if !finalRUReceived { 241 t.Error("Last resource-usage report from finished process was not received.") 242 } 243 244 // We expect floor(4200 / 2000) regular resource-usage reports, plus the last one sent after 245 // the process terminates. 246 if ruCnt != 3 { 247 t.Errorf("Unexpected number of resource-usage reports received. Got %d. Want 3.", ruCnt) 248 } 249 } 250 251 func mustNew(t *testing.T, name string, cfg *dspb.Config, sc service.Context) *Execution { 252 t.Helper() 253 ex, err := New(context.Background(), name, cfg, sc) 254 if err != nil { 255 t.Fatalf("execution.New returned error: %v", err) 256 } 257 t.Cleanup(func() { 258 ex.Shutdown() 259 err := ex.Wait() 260 if err != nil { 261 t.Errorf("unexpected execution error: %v", err) 262 } 263 }) 264 return ex 265 } 266 267 func init() { 268 // Hack so that the testclient logs get written to the right place. 269 if d := os.Getenv("TEST_UNDECLARED_OUTPUTS_DIR"); d != "" { 270 os.Setenv("GOOGLE_LOG_DIR", d) 271 } 272 }