exec_test.go
  1  package util
  2  
  3  import (
  4  	"context"
  5  	"sync/atomic"
  6  	"testing"
  7  	"time"
  8  
  9  	"github.com/stretchr/testify/assert"
 10  	"github.com/symflower/eval-dev-quality/log"
 11  )
 12  
 13  func TestCommandWithResultTimeout(t *testing.T) {
 14  	logOutput, logger := log.Buffer()
 15  	defer func() {
 16  		if t.Failed() {
 17  			t.Log(logOutput.String())
 18  		}
 19  	}()
 20  
 21  	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(1*time.Second))
 22  	defer cancel()
 23  
 24  	start := time.Now()
 25  	_, err := CommandWithResult(ctx, logger, &Command{
 26  		Command: []string{
 27  			"sleep",
 28  			"60",
 29  		},
 30  	})
 31  	duration := time.Since(start)
 32  
 33  	assert.Error(t, err)
 34  	assert.Less(t, duration.Seconds(), 5.0)
 35  }
 36  
 37  func TestFilterArgs(t *testing.T) {
 38  	type testCase struct {
 39  		Name string
 40  
 41  		Args   []string
 42  		Filter []string
 43  		Ignore bool
 44  
 45  		ExpectedFiltered []string
 46  	}
 47  
 48  	validate := func(t *testing.T, tc *testCase) {
 49  		t.Run(tc.Name, func(t *testing.T) {
 50  			actualFiltered := FilterArgs(tc.Args, tc.Filter, tc.Ignore)
 51  
 52  			assert.Equal(t, tc.ExpectedFiltered, actualFiltered)
 53  		})
 54  	}
 55  
 56  	validate(t, &testCase{
 57  		Name: "Filter arguments",
 58  
 59  		Args: []string{
 60  			"--runtime",
 61  			"abc",
 62  			"--runs",
 63  			"5",
 64  		},
 65  		Filter: []string{
 66  			"runtime",
 67  		},
 68  		Ignore: true,
 69  
 70  		ExpectedFiltered: []string{
 71  			"--runs",
 72  			"5",
 73  		},
 74  	})
 75  
 76  	validate(t, &testCase{
 77  		Name: "Filter arguments with equals sign",
 78  
 79  		Args: []string{
 80  			"--runtime=abc",
 81  			"--runs=5",
 82  			"--foo",
 83  			"bar",
 84  		},
 85  		Filter: []string{
 86  			"runtime",
 87  		},
 88  		Ignore: true,
 89  
 90  		ExpectedFiltered: []string{
 91  			"--runs",
 92  			"5",
 93  			"--foo",
 94  			"bar",
 95  		},
 96  	})
 97  
 98  	validate(t, &testCase{
 99  		Name: "Filter arguments with an allow list",
100  
101  		Args: []string{
102  			"--runtime=abc",
103  			"--runs=5",
104  			"--foo",
105  			"bar",
106  		},
107  
108  		Filter: []string{
109  			"runtime",
110  		},
111  
112  		ExpectedFiltered: []string{
113  			"--runtime",
114  			"abc",
115  		},
116  	})
117  }
118  
119  func TestFilterArgsKeep(t *testing.T) {
120  	type testCase struct {
121  		Name string
122  
123  		Args   []string
124  		Filter []string
125  
126  		ExpectedFiltered []string
127  	}
128  
129  	validate := func(t *testing.T, tc *testCase) {
130  		t.Run(tc.Name, func(t *testing.T) {
131  			actualFiltered := FilterArgsKeep(tc.Args, tc.Filter)
132  
133  			assert.Equal(t, tc.ExpectedFiltered, actualFiltered)
134  		})
135  	}
136  
137  	validate(t, &testCase{
138  		Name: "Keep arguments",
139  
140  		Args: []string{
141  			"--runtime=abc",
142  			"--runs=5",
143  			"--foo",
144  			"bar",
145  		},
146  
147  		Filter: []string{
148  			"runtime",
149  		},
150  
151  		ExpectedFiltered: []string{
152  			"--runtime",
153  			"abc",
154  		},
155  	})
156  
157  	validate(t, &testCase{
158  		Name: "Multiple arguments",
159  
160  		Args: []string{
161  			"--runtime=abc",
162  			"--repository=abc",
163  			"--repository=def",
164  			"--foo",
165  			"bar",
166  		},
167  
168  		Filter: []string{
169  			"runtime",
170  			"repository",
171  		},
172  
173  		ExpectedFiltered: []string{
174  			"--runtime",
175  			"abc",
176  			"--repository",
177  			"abc",
178  			"--repository",
179  			"def",
180  		},
181  	})
182  }
183  
184  func TestFilterArgsRemove(t *testing.T) {
185  	type testCase struct {
186  		Name string
187  
188  		Args   []string
189  		Filter []string
190  
191  		ExpectedFiltered []string
192  	}
193  
194  	validate := func(t *testing.T, tc *testCase) {
195  		t.Run(tc.Name, func(t *testing.T) {
196  			actualFiltered := FilterArgsRemove(tc.Args, tc.Filter)
197  
198  			assert.Equal(t, tc.ExpectedFiltered, actualFiltered)
199  		})
200  	}
201  
202  	validate(t, &testCase{
203  		Name: "Remove arguments",
204  
205  		Args: []string{
206  			"--runtime",
207  			"abc",
208  			"--runs",
209  			"5",
210  		},
211  		Filter: []string{
212  			"runtime",
213  		},
214  
215  		ExpectedFiltered: []string{
216  			"--runs",
217  			"5",
218  		},
219  	})
220  }
221  
222  func TestParallelExecute(t *testing.T) {
223  	type testCase struct {
224  		Limit uint
225  		Times uint
226  
227  		Function func()
228  	}
229  
230  	run := func(tc *testCase) {
231  		p := NewParallel(tc.Limit)
232  		for i := uint(0); i < tc.Times; i++ {
233  			p.Execute(tc.Function)
234  		}
235  		p.Wait()
236  	}
237  
238  	t.Run("Atomic count", func(t *testing.T) {
239  		var count atomic.Uint32
240  		expectedCount := uint(10)
241  
242  		tc := &testCase{
243  			Limit: 2,
244  			Times: expectedCount,
245  
246  			Function: func() {
247  				count.Add(1)
248  			},
249  		}
250  
251  		run(tc)
252  
253  		assert.Equal(t, count.Load(), uint32(expectedCount))
254  	})
255  
256  	t.Run("Timed limiting", func(t *testing.T) {
257  		tc := &testCase{
258  			Limit: 1,
259  			Times: 3,
260  
261  			Function: func() {
262  				time.Sleep(500 * time.Millisecond)
263  			},
264  		}
265  
266  		start := time.Now()
267  		run(tc)
268  		duration := time.Since(start)
269  
270  		assert.GreaterOrEqual(t, duration, 1500*time.Millisecond)
271  	})
272  
273  }
274  
275  func TestFlags(t *testing.T) {
276  	type testCase struct {
277  		Name string
278  
279  		Cmd any
280  
281  		ExpectedArgs []string
282  	}
283  
284  	validate := func(t *testing.T, tc *testCase) {
285  		t.Run(tc.Name, func(t *testing.T) {
286  			actualArgs := Flags(tc.Cmd)
287  
288  			assert.Equal(t, tc.ExpectedArgs, actualArgs)
289  		})
290  	}
291  
292  	validate(t, &testCase{
293  		Name: "Struct Command",
294  
295  		Cmd: struct {
296  			PropA string `long:"PropA"`
297  			PropB string `long:"PropB"`
298  			PropC string `short:"hey"`
299  		}{},
300  
301  		ExpectedArgs: []string{
302  			"PropA",
303  			"PropB",
304  		},
305  	})
306  
307  	validate(t, &testCase{
308  		Name: "Pointer Struct Command",
309  
310  		Cmd: &struct {
311  			PropA string `long:"PropA"`
312  			PropB string `long:"PropB"`
313  		}{},
314  
315  		ExpectedArgs: []string{
316  			"PropA",
317  			"PropB",
318  		},
319  	})
320  }