testhelpers.go 1.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
  1. package filterutil
  2. import (
  3. "sort"
  4. "strings"
  5. "github.com/opencost/opencost/pkg/filter21/ast"
  6. )
  7. func testingOnlyLess(left, right ast.FilterNode) bool {
  8. leftStr := ast.ToPreOrderShortString(left)
  9. rightStr := ast.ToPreOrderShortString(right)
  10. return strings.Compare(leftStr, rightStr) < 0
  11. }
  12. func testingOnlySortedOperands(operands []ast.FilterNode) []ast.FilterNode {
  13. var copy []ast.FilterNode
  14. for _, operand := range operands {
  15. copy = append(copy, operand)
  16. }
  17. sort.SliceStable(copy, func(i, j int) bool {
  18. leftSorted := TestingOnlySortNode(copy[i])
  19. rightSorted := TestingOnlySortNode(copy[j])
  20. return testingOnlyLess(leftSorted, rightSorted)
  21. })
  22. return copy
  23. }
  24. // TestingOnlySortNode sorts the provided node deterministically, intended only
  25. // for use in unit tests to ensure that filter parsing steps produce logically-
  26. // equivalent filters. This is useful only for cases where filters are
  27. // constructed nondeterministically, like via a map iteration.
  28. func TestingOnlySortNode(n ast.FilterNode) ast.FilterNode {
  29. switch concrete := n.(type) {
  30. case *ast.AndOp:
  31. return &ast.AndOp{
  32. Operands: testingOnlySortedOperands(concrete.Operands),
  33. }
  34. case *ast.OrOp:
  35. return &ast.OrOp{
  36. Operands: testingOnlySortedOperands(concrete.Operands),
  37. }
  38. case *ast.NotOp:
  39. return &ast.NotOp{
  40. Operand: TestingOnlySortNode(concrete.Operand),
  41. }
  42. // This isn't great, but non-container ops are mostly safe. We don't need
  43. // full deepcopy because this is for testing-only comparison
  44. default:
  45. return concrete
  46. }
  47. }