Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Android/build/build/blueprint/   (Android Betriebssystem Version 17©)  Datei vom 26.5.2026 mit Größe 42 kB image not shown  

Quelle  context_test.go   Sprache: unbekannt

 
Spracherkennung für: .go vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

// Copyright 2014 Google Inc. All rights reserved.
//
// 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 blueprint

import (
 "bytes"
 "errors"
 "fmt"
 "hash/fnv"
 "reflect"
 "slices"
 "strconv"
 "strings"
 "sync"
 "testing"
 "time"

 "github.com/google/blueprint/gobtools"
 "github.com/google/blueprint/parser"
 "github.com/google/blueprint/proptools"
 "github.com/google/blueprint/uniquelist"
)

type Walker interface {
 Walk() bool
}

func walkDependencyGraph(ctx *Context, topModule *moduleInfo, allowDuplicates bool) (string, string) {
 var outputDown string
 var outputUp string
 ctx.walkDeps(topModule, allowDuplicates,
  func(dep depInfo, parent *moduleInfo) bool {
   outputDown += ctx.ModuleName(dep.module.logicModule)
   if tag, ok := dep.tag.(walkerDepsTag); ok {
    if !tag.follow {
     return false
    }
   }
   if dep.module.logicModule.(Walker).Walk() {
    return true
   }

   return false
  },
  func(dep depInfo, parent *moduleInfo) {
   outputUp += ctx.ModuleName(dep.module.logicModule)
  })
 return outputDown, outputUp
}

type depsProvider interface {
 Deps() []string
 IgnoreDeps() []string
}

type IncrementalTestInfo struct {
 Value string
}

var IncrementalTestProviderKey = NewProvider[IncrementalTestInfo]()

func init() {
 IncrementalTestProviderGobRegId = gobtools.RegisterType(func() gobtools.CustomDec { return new(IncrementalTestInfo) })
}

func (r IncrementalTestInfo) Encode(ctx gobtools.EncContext, buf *bytes.Buffer) error {
 var err error

 if err = gobtools.EncodeString(buf, r.Value); err != nil {
  return err
 }
 return err
}

func (r *IncrementalTestInfo) Decode(ctx gobtools.EncContext, buf *bytes.Reader) error {
 var err error

 err = gobtools.DecodeString(buf, &r.Value)
 if err != nil {
  return err
 }

 return err
}

func (r IncrementalTestInfo) CustomHash(hasher *proptools.Hasher) error {
 hasher.HashType(reflect.TypeOf(r))
 hasher.WriteString(r.Value)
 return nil
}

var IncrementalTestProviderGobRegId int16

func (r IncrementalTestInfo) GetTypeId() int16 {
 return IncrementalTestProviderGobRegId
}

type baseTestModule struct {
 ModuleBase
 SimpleName
 properties struct {
  Deps             []string
  Ignored_deps     []string
  Outputs          []string
  Order_only       []string
  Extra_outputs    []string
  Extra_order_only []string
  Srcs             []string
  Exclude_srcs     []string
 }
 GenerateBuildActionsCalled bool
}

func (b *baseTestModule) Deps() []string {
 return b.properties.Deps
}

func (b *baseTestModule) IgnoreDeps() []string {
 return b.properties.Ignored_deps
}

var pctx PackageContext

func init() {
 pctx = NewPackageContext("android/blueprint")
}
func (b *baseTestModule) GenerateBuildActions(ctx ModuleContext) {
 b.GenerateBuildActionsCalled = true
 ctx.Build(pctx, BuildParams{
  Rule:      Phony,
  Outputs:   b.properties.Outputs,
  OrderOnly: b.properties.Order_only,
 })
 if len(b.properties.Extra_outputs) > 0 {
  ctx.Build(pctx, BuildParams{
   Rule:      Phony,
   Outputs:   b.properties.Extra_outputs,
   OrderOnly: b.properties.Extra_order_only,
  })
 }
 for _, src := range b.properties.Srcs {
  ctx.GlobWithDeps(src, b.properties.Exclude_srcs)
 }
 ctx.VisitDirectDeps(func(module Module) {
  OtherModuleProvider(ctx, module, IncrementalTestProviderKey)
 })
 SetProvider(ctx, IncrementalTestProviderKey, IncrementalTestInfo{
  Value: ctx.ModuleName(),
 })
}

type fooModule struct {
 baseTestModule
}

func newFooModule() (Module, []interface{}) {
 m := &fooModule{}
 return m, []interface{}{&m.baseTestModule.properties, &m.SimpleName.Properties}
}

func (f *fooModule) Walk() bool {
 return true
}

type barModule struct {
 baseTestModule
}

func newBarModule() (Module, []interface{}) {
 m := &barModule{}
 return m, []interface{}{&m.baseTestModule.properties, &m.SimpleName.Properties}
}

func (b *barModule) Walk() bool {
 return false
}

type walkerDepsTag struct {
 BaseDependencyTag
 // True if the dependency should be followed, false otherwise.
 follow bool
}

func depsMutator(mctx BottomUpMutatorContext) {
 if m, ok := mctx.Module().(depsProvider); ok {
  mctx.AddDependency(mctx.Module(), walkerDepsTag{follow: false}, m.IgnoreDeps()...)
  mctx.AddDependency(mctx.Module(), walkerDepsTag{follow: true}, m.Deps()...)
 }
}

func TestContextParse(t *testing.T) {
 ctx := NewContext()
 ctx.RegisterModuleType("foo_module", newFooModule)
 ctx.RegisterModuleType("bar_module", newBarModule)

 r := bytes.NewBufferString(`
  foo_module {
         name: "MyFooModule",
   deps: ["MyBarModule"],
  }

  bar_module {
         name: "MyBarModule",
  }
 `)

 _, _, errs := ctx.parseOne(".", "Blueprint", r, parser.NewScope(nil), nil)
 if len(errs) > 0 {
  t.Errorf("unexpected parse errors:")
  for _, err := range errs {
   t.Errorf("  %s", err)
  }
  t.FailNow()
 }

 _, errs = ctx.ResolveDependencies(nil)
 if len(errs) > 0 {
  t.Errorf("unexpected dep errors:")
  for _, err := range errs {
   t.Errorf("  %s", err)
  }
  t.FailNow()
 }
}

// > |===B---D       - represents a non-walkable edge
// > A               = represents a walkable edge
// > |===C===E---G
// >     |       |   A should not be visited because it's the root node.
// >     |===F===|   B, D and E should not be walked.
func TestWalkDeps(t *testing.T) {
 ctx := NewContext()
 ctx.MockFileSystem(map[string][]byte{
  "Android.bp": []byte(`
   foo_module {
       name: "A",
       deps: ["B", "C"],
   }

   bar_module {
       name: "B",
       deps: ["D"],
   }

   foo_module {
       name: "C",
       deps: ["E", "F"],
   }

   foo_module {
       name: "D",
   }

   bar_module {
       name: "E",
       deps: ["G"],
   }

   foo_module {
       name: "F",
       deps: ["G"],
   }

   foo_module {
       name: "G",
   }
  `),
 })

 ctx.RegisterModuleType("foo_module", newFooModule)
 ctx.RegisterModuleType("bar_module", newBarModule)
 ctx.RegisterBottomUpMutator("deps", depsMutator)
 _, errs := ctx.ParseBlueprintsFiles("Android.bp", nil)
 if len(errs) > 0 {
  t.Errorf("unexpected parse errors:")
  for _, err := range errs {
   t.Errorf("  %s", err)
  }
  t.FailNow()
 }

 _, errs = ctx.ResolveDependencies(nil)
 if len(errs) > 0 {
  t.Errorf("unexpected dep errors:")
  for _, err := range errs {
   t.Errorf("  %s", err)
  }
  t.FailNow()
 }

 topModule := ctx.moduleGroupFromName("A", nil).modules.firstModule()
 outputDown, outputUp := walkDependencyGraph(ctx, topModule, false)
 if outputDown != "BCEFG" {
  t.Errorf("unexpected walkDeps behaviour: %s\ndown should be: BCEFG", outputDown)
 }
 if outputUp != "BEGFC" {
  t.Errorf("unexpected walkDeps behaviour: %s\nup should be: BEGFC", outputUp)
 }
}

// > |===B---D           - represents a non-walkable edge
// > A                   = represents a walkable edge
// > |===C===E===\       A should not be visited because it's the root node.
// >     |       |       B, D should not be walked.
// >     |===F===G===H   G should be visited multiple times
// >         \===/       H should only be visited once
func TestWalkDepsDuplicates(t *testing.T) {
 ctx := NewContext()
 ctx.MockFileSystem(map[string][]byte{
  "Android.bp": []byte(`
   foo_module {
       name: "A",
       deps: ["B", "C"],
   }

   bar_module {
       name: "B",
       deps: ["D"],
   }

   foo_module {
       name: "C",
       deps: ["E", "F"],
   }

   foo_module {
       name: "D",
   }

   foo_module {
       name: "E",
       deps: ["G"],
   }

   foo_module {
       name: "F",
       deps: ["G", "G"],
   }

   foo_module {
       name: "G",
    deps: ["H"],
   }

   foo_module {
       name: "H",
   }
  `),
 })

 ctx.RegisterModuleType("foo_module", newFooModule)
 ctx.RegisterModuleType("bar_module", newBarModule)
 ctx.RegisterBottomUpMutator("deps", depsMutator)
 _, errs := ctx.ParseBlueprintsFiles("Android.bp", nil)
 if len(errs) > 0 {
  t.Errorf("unexpected parse errors:")
  for _, err := range errs {
   t.Errorf("  %s", err)
  }
  t.FailNow()
 }

 _, errs = ctx.ResolveDependencies(nil)
 if len(errs) > 0 {
  t.Errorf("unexpected dep errors:")
  for _, err := range errs {
   t.Errorf("  %s", err)
  }
  t.FailNow()
 }

 topModule := ctx.moduleGroupFromName("A", nil).modules.firstModule()
 outputDown, outputUp := walkDependencyGraph(ctx, topModule, true)
 if outputDown != "BCEGHFGG" {
  t.Errorf("unexpected walkDeps behaviour: %s\ndown should be: BCEGHFGG", outputDown)
 }
 if outputUp != "BHGEGGFC" {
  t.Errorf("unexpected walkDeps behaviour: %s\nup should be: BHGEGGFC", outputUp)
 }
}

// >                     - represents a non-walkable edge
// > A                   = represents a walkable edge
// > |===B-------\       A should not be visited because it's the root node.
// >     |       |       B -> D should not be walked.
// >     |===C===D===E   B -> C -> D -> E should be walked
func TestWalkDepsDuplicates_IgnoreFirstPath(t *testing.T) {
 ctx := NewContext()
 ctx.MockFileSystem(map[string][]byte{
  "Android.bp": []byte(`
   foo_module {
       name: "A",
       deps: ["B"],
   }

   foo_module {
       name: "B",
       deps: ["C"],
       ignored_deps: ["D"],
   }

   foo_module {
       name: "C",
       deps: ["D"],
   }

   foo_module {
       name: "D",
       deps: ["E"],
   }

   foo_module {
       name: "E",
   }
  `),
 })

 ctx.RegisterModuleType("foo_module", newFooModule)
 ctx.RegisterModuleType("bar_module", newBarModule)
 ctx.RegisterBottomUpMutator("deps", depsMutator)
 _, errs := ctx.ParseBlueprintsFiles("Android.bp", nil)
 if len(errs) > 0 {
  t.Errorf("unexpected parse errors:")
  for _, err := range errs {
   t.Errorf("  %s", err)
  }
  t.FailNow()
 }

 _, errs = ctx.ResolveDependencies(nil)
 if len(errs) > 0 {
  t.Errorf("unexpected dep errors:")
  for _, err := range errs {
   t.Errorf("  %s", err)
  }
  t.FailNow()
 }

 topModule := ctx.moduleGroupFromName("A", nil).modules.firstModule()
 outputDown, outputUp := walkDependencyGraph(ctx, topModule, true)
 expectedDown := "BDCDE"
 if outputDown != expectedDown {
  t.Errorf("unexpected walkDeps behaviour: %s\ndown should be: %s", outputDown, expectedDown)
 }
 expectedUp := "DEDCB"
 if outputUp != expectedUp {
  t.Errorf("unexpected walkDeps behaviour: %s\nup should be: %s", outputUp, expectedUp)
 }
}

func TestCreateModule(t *testing.T) {
 ctx := newContext()
 ctx.MockFileSystem(map[string][]byte{
  "Android.bp": []byte(`
   foo_module {
       name: "A",
       deps: ["B", "C"],
   }
  `),
 })

 ctx.RegisterBottomUpMutator("create", createTestMutator).UsesCreateModule()
 ctx.RegisterBottomUpMutator("deps", depsMutator)

 ctx.RegisterModuleType("foo_module", newFooModule)
 ctx.RegisterModuleType("bar_module", newBarModule)
 _, errs := ctx.ParseBlueprintsFiles("Android.bp", nil)
 if len(errs) > 0 {
  t.Errorf("unexpected parse errors:")
  for _, err := range errs {
   t.Errorf("  %s", err)
  }
  t.FailNow()
 }

 _, errs = ctx.ResolveDependencies(nil)
 if len(errs) > 0 {
  t.Errorf("unexpected dep errors:")
  for _, err := range errs {
   t.Errorf("  %s", err)
  }
  t.FailNow()
 }

 a := ctx.moduleGroupFromName("A", nil).modules.firstModule().logicModule.(*fooModule)
 b := ctx.moduleGroupFromName("B", nil).modules.firstModule().logicModule.(*barModule)
 c := ctx.moduleGroupFromName("C", nil).modules.firstModule().logicModule.(*barModule)
 d := ctx.moduleGroupFromName("D", nil).modules.firstModule().logicModule.(*fooModule)

 checkDeps := func(m Module, expected string) {
  var deps []string
  ctx.VisitDirectDeps(m, func(m Module) {
   deps = append(deps, ctx.ModuleName(m))
  })
  got := strings.Join(deps, ",")
  if got != expected {
   t.Errorf("unexpected %q dependencies, got %q expected %q",
    ctx.ModuleName(m), got, expected)
  }
 }

 checkDeps(a, "B,C")
 checkDeps(b, "D")
 checkDeps(c, "D")
 checkDeps(d, "")
}

func createTestMutator(ctx BottomUpMutatorContext) {
 type props struct {
  Name string
  Deps []string
 }

 ctx.CreateModule(newBarModule, "new_bar", &props{
  Name: "B",
  Deps: []string{"D"},
 })

 ctx.CreateModule(newBarModule, "new_bar", &props{
  Name: "C",
  Deps: []string{"D"},
 })

 ctx.CreateModule(newFooModule, "new_foo", &props{
  Name: "D",
 })
}

func TestWalkFileOrder(t *testing.T) {
 // Run the test once to see how long it normally takes
 start := time.Now()
 doTestWalkFileOrder(t, time.Duration(0))
 duration := time.Since(start)

 // Run the test again, but put enough of a sleep into each visitor to detect ordering
 // problems if they exist
 doTestWalkFileOrder(t, duration)
}

// test that WalkBlueprintsFiles calls asyncVisitor in the right order
func doTestWalkFileOrder(t *testing.T, sleepDuration time.Duration) {
 // setup mock context
 ctx := newContext()
 mockFiles := map[string][]byte{
  "Android.bp": []byte(`
   sample_module {
       name: "a",
   }
  `),
  "dir1/Android.bp": []byte(`
   sample_module {
       name: "b",
   }
  `),
  "dir1/dir2/Android.bp": []byte(`
   sample_module {
       name: "c",
   }
  `),
 }
 ctx.MockFileSystem(mockFiles)

 // prepare to monitor the visit order
 visitOrder := []string{}
 visitLock := sync.Mutex{}
 correctVisitOrder := []string{"Android.bp", "dir1/Android.bp", "dir1/dir2/Android.bp"}

 // sleep longer when processing the earlier files
 chooseSleepDuration := func(fileName string) (duration time.Duration) {
  duration = time.Duration(0)
  for i := len(correctVisitOrder) - 1; i >= 0; i-- {
   if fileName == correctVisitOrder[i] {
    return duration
   }
   duration = duration + sleepDuration
  }
  panic("unrecognized file name " + fileName)
 }

 visitor := func(file *parser.File) {
  time.Sleep(chooseSleepDuration(file.Name))
  visitLock.Lock()
  defer visitLock.Unlock()
  visitOrder = append(visitOrder, file.Name)
 }
 keys := []string{"Android.bp", "dir1/Android.bp", "dir1/dir2/Android.bp"}

 // visit the blueprints files
 ctx.WalkBlueprintsFiles(".", keys, visitor)

 // check the order
 if !reflect.DeepEqual(visitOrder, correctVisitOrder) {
  t.Errorf("Incorrect visit order; expected %v, got %v", correctVisitOrder, visitOrder)
 }
}

// test that WalkBlueprintsFiles reports syntax errors
func TestWalkingWithSyntaxError(t *testing.T) {
 // setup mock context
 ctx := newContext()
 mockFiles := map[string][]byte{
  "Android.bp": []byte(`
   sample_module {
       name: "a" "b",
   }
  `),
  "dir1/Android.bp": []byte(`
   sample_module {
       name: "b",
  `),
  "dir1/dir2/Android.bp": []byte(`
   sample_module {
       name: "c",
   }
  `),
 }
 ctx.MockFileSystem(mockFiles)

 keys := []string{"Android.bp", "dir1/Android.bp", "dir1/dir2/Android.bp"}

 // visit the blueprints files
 _, errs := ctx.WalkBlueprintsFiles(".", keys, func(file *parser.File) {})

 expectedErrs := []error{
  errors.New(`Android.bp:3:18: expected "}", found String`),
  errors.New(`dir1/Android.bp:4:3: expected "}", found EOF`),
 }
 if fmt.Sprintf("%s", expectedErrs) != fmt.Sprintf("%s", errs) {
  t.Errorf("Incorrect errors; expected:\n%s\ngot:\n%s", expectedErrs, errs)
 }

}

func TestParseFailsForModuleWithoutName(t *testing.T) {
 ctx := NewContext()
 ctx.MockFileSystem(map[string][]byte{
  "Android.bp": []byte(`
   foo_module {
       name: "A",
   }

   bar_module {
       deps: ["A"],
   }
  `),
 })
 ctx.RegisterModuleType("foo_module", newFooModule)
 ctx.RegisterModuleType("bar_module", newBarModule)

 _, errs := ctx.ParseBlueprintsFiles("Android.bp", nil)

 expectedErrs := []error{
  errors.New(`Android.bp:6:4: property 'name' is missing from a module`),
 }
 if fmt.Sprintf("%s", expectedErrs) != fmt.Sprintf("%s", errs) {
  t.Errorf("Incorrect errors; expected:\n%s\ngot:\n%s", expectedErrs, errs)
 }
}

func Test_findVariant(t *testing.T) {
 module := &moduleInfo{
  variant: variant{
   name: "normal_local",
   variations: variationMap{
    map[string]string{
     "normal": "normal",
    },
   },
  },
 }

 makeDependencyGroup := func(in ...*moduleInfo) *moduleGroup {
  group := &moduleGroup{
   name: "dep",
  }
  for _, m := range in {
   m.group = group
   group.modules = append(group.modules, m)
  }
  return group
 }

 tests := []struct {
  name         string
  possibleDeps *moduleGroup
  variations   []Variation
  far          bool
  reverse      bool
  want         string
 }{
  {
   name: "AddVariationDependencies(nil)",
   // A dependency that matches the non-local variations of the module
   possibleDeps: makeDependencyGroup(
    &moduleInfo{
     variant: variant{
      name: "normal",
      variations: variationMap{
       map[string]string{
        "normal": "normal",
       },
      },
     },
    },
   ),
   variations: nil,
   far:        false,
   reverse:    false,
   want:       "normal",
  },
  {
   name: "AddVariationDependencies(a)",
   // A dependency with local variations
   possibleDeps: makeDependencyGroup(
    &moduleInfo{
     variant: variant{
      name: "normal_a",
      variations: variationMap{
       map[string]string{
        "normal": "normal",
        "a":      "a",
       },
      },
     },
    },
   ),
   variations: []Variation{{"a", "a"}},
   far:        false,
   reverse:    false,
   want:       "normal_a",
  },
  {
   name: "AddFarVariationDependencies(far)",
   // A dependency with far variations
   possibleDeps: makeDependencyGroup(
    &moduleInfo{
     variant: variant{
      name:       "",
      variations: variationMap{},
     },
    },
    &moduleInfo{
     variant: variant{
      name: "far",
      variations: variationMap{
       map[string]string{
        "far": "far",
       },
      },
     },
    },
   ),
   variations: []Variation{{"far", "far"}},
   far:        true,
   reverse:    false,
   want:       "far",
  },
 }
 for _, tt := range tests {
  t.Run(tt.name, func(t *testing.T) {
   ctx := NewContext()
   got, _, errs := ctx.findVariant(nil, module, nil, tt.possibleDeps, tt.variations, tt.far, tt.reverse, -1)
   if errs != nil {
    t.Fatal(errs)
   }
   if g, w := got == nil, tt.want == "nil"; g != w {
    t.Fatalf("findVariant() got = %v, want %v", got, tt.want)
   }
   if got != nil {
    if g, w := got.String(), fmt.Sprintf("module %q variant %q", "dep", tt.want); g != w {
     t.Errorf("findVariant() got = %v, want %v", g, w)
    }
   }
  })
 }
}

func Test_parallelVisit(t *testing.T) {
 addDep := func(from, to *moduleInfo) {
  from.directDeps = append(from.directDeps, depInfo{to, nil})
  from.forwardDeps = append(from.forwardDeps, to)
  to.reverseDeps = append(to.reverseDeps, from)
 }

 create := func(name string) *moduleInfo {
  m := &moduleInfo{
   group: &moduleGroup{
    name: name,
   },
  }
  m.group.modules = moduleList{m}
  return m
 }
 moduleA := create("A")
 moduleB := create("B")
 moduleC := create("C")
 moduleD := create("D")
 moduleE := create("E")
 moduleF := create("F")
 moduleG := create("G")

 moduleH := create("H")
 moduleI := create("I")
 moduleJ := create("J")

 // A depends on B, B depends on C.
 addDep(moduleA, moduleB)
 addDep(moduleB, moduleC)

 // Nothing depends on D through G, and they don't depend on anything.

 // H depends on I, and I and J depend on each other.
 addDep(moduleH, moduleI)
 addDep(moduleI, moduleJ)
 addDep(moduleJ, moduleI)

 t.Run("no modules", func(t *testing.T) {
  errs := parallelVisit(slices.Values([]*moduleInfo(nil)), bottomUpVisitorImpl{}, 1,
   func(module *moduleInfo, pause pauseFunc) bool {
    panic("unexpected call to visitor")
   })
  if errs != nil {
   t.Errorf("expected no errors, got %q", errs)
  }
 })
 t.Run("bottom up", func(t *testing.T) {
  order := ""
  errs := parallelVisit(slices.Values([]*moduleInfo{moduleA, moduleB, moduleC}), bottomUpVisitorImpl{}, 1,
   func(module *moduleInfo, pause pauseFunc) bool {
    order += module.group.name
    return false
   })
  if errs != nil {
   t.Errorf("expected no errors, got %q", errs)
  }
  if g, w := order, "CBA"; g != w {
   t.Errorf("expected order %q, got %q", w, g)
  }
 })
 t.Run("pause", func(t *testing.T) {
  order := ""
  errs := parallelVisit(slices.Values([]*moduleInfo{moduleA, moduleB, moduleC, moduleD}), bottomUpVisitorImpl{}, 1,
   func(module *moduleInfo, pause pauseFunc) bool {
    if module == moduleC {
     // Pause module C on module D
     pause(moduleD)
    }
    order += module.group.name
    return false
   })
  if errs != nil {
   t.Errorf("expected no errors, got %q", errs)
  }
  if g, w := order, "DCBA"; g != w {
   t.Errorf("expected order %q, got %q", w, g)
  }
 })
 t.Run("cancel", func(t *testing.T) {
  order := ""
  errs := parallelVisit(slices.Values([]*moduleInfo{moduleA, moduleB, moduleC}), bottomUpVisitorImpl{}, 1,
   func(module *moduleInfo, pause pauseFunc) bool {
    order += module.group.name
    // Cancel in module B
    return module == moduleB
   })
  if errs != nil {
   t.Errorf("expected no errors, got %q", errs)
  }
  if g, w := order, "CB"; g != w {
   t.Errorf("expected order %q, got %q", w, g)
  }
 })
 t.Run("pause and cancel", func(t *testing.T) {
  order := ""
  errs := parallelVisit(slices.Values([]*moduleInfo{moduleA, moduleB, moduleC, moduleD}), bottomUpVisitorImpl{}, 1,
   func(module *moduleInfo, pause pauseFunc) bool {
    if module == moduleC {
     // Pause module C on module D
     pause(moduleD)
    }
    order += module.group.name
    // Cancel in module D
    return module == moduleD
   })
  if errs != nil {
   t.Errorf("expected no errors, got %q", errs)
  }
  if g, w := order, "D"; g != w {
   t.Errorf("expected order %q, got %q", w, g)
  }
 })
 t.Run("parallel", func(t *testing.T) {
  order := ""
  errs := parallelVisit(slices.Values([]*moduleInfo{moduleA, moduleB, moduleC}), bottomUpVisitorImpl{}, 3,
   func(module *moduleInfo, pause pauseFunc) bool {
    order += module.group.name
    return false
   })
  if errs != nil {
   t.Errorf("expected no errors, got %q", errs)
  }
  if g, w := order, "CBA"; g != w {
   t.Errorf("expected order %q, got %q", w, g)
  }
 })
 t.Run("pause existing", func(t *testing.T) {
  order := ""
  errs := parallelVisit(slices.Values([]*moduleInfo{moduleA, moduleB, moduleC}), bottomUpVisitorImpl{}, 3,
   func(module *moduleInfo, pause pauseFunc) bool {
    if module == moduleA {
     // Pause module A on module B (an existing dependency)
     pause(moduleB)
    }
    order += module.group.name
    return false
   })
  if errs != nil {
   t.Errorf("expected no errors, got %q", errs)
  }
  if g, w := order, "CBA"; g != w {
   t.Errorf("expected order %q, got %q", w, g)
  }
 })
 t.Run("cycle", func(t *testing.T) {
  errs := parallelVisit(slices.Values([]*moduleInfo{moduleA, moduleB, moduleC}), bottomUpVisitorImpl{}, 3,
   func(module *moduleInfo, pause pauseFunc) bool {
    if module == moduleC {
     // Pause module C on module A (a dependency cycle)
     pause(moduleA)
    }
    return false
   })
  want := []string{
   `encountered dependency cycle`,
   `module "C" depends on module "A"`,
   `module "A" depends on module "B"`,
   `module "B" depends on module "C"`,
  }
  for i := range want {
   if len(errs) <= i {
    t.Errorf("missing error %s", want[i])
   } else if !strings.Contains(errs[i].Error(), want[i]) {
    t.Errorf("expected error %s, got %s", want[i], errs[i])
   }
  }
  if len(errs) > len(want) {
   for _, err := range errs[len(want):] {
    t.Errorf("unexpected error %s", err.Error())
   }
  }
 })
 t.Run("pause cycle", func(t *testing.T) {
  errs := parallelVisit(slices.Values([]*moduleInfo{moduleA, moduleB, moduleC, moduleD}), bottomUpVisitorImpl{}, 3,
   func(module *moduleInfo, pause pauseFunc) bool {
    if module == moduleC {
     // Pause module C on module D
     pause(moduleD)
    }
    if module == moduleD {
     // Pause module D on module C (a pause cycle)
     pause(moduleC)
    }
    return false
   })
  want := []string{
   `encountered dependency cycle`,
   `module "D" depends on module "C"`,
   `module "C" depends on module "D"`,
  }
  for i := range want {
   if len(errs) <= i {
    t.Errorf("missing error %s", want[i])
   } else if !strings.Contains(errs[i].Error(), want[i]) {
    t.Errorf("expected error %s, got %s", want[i], errs[i])
   }
  }
  if len(errs) > len(want) {
   for _, err := range errs[len(want):] {
    t.Errorf("unexpected error %s", err.Error())
   }
  }
 })
 t.Run("pause cycle with deps", func(t *testing.T) {
  pauseDeps := map[*moduleInfo]*moduleInfo{
   // F and G form a pause cycle
   moduleF: moduleG,
   moduleG: moduleF,
   // D depends on E which depends on the pause cycle, making E the first alphabetical
   // entry in pauseMap, which is not part of the cycle.
   moduleD: moduleE,
   moduleE: moduleF,
  }
  errs := parallelVisit(slices.Values([]*moduleInfo{moduleD, moduleE, moduleF, moduleG}), bottomUpVisitorImpl{}, 4,
   func(module *moduleInfo, pause pauseFunc) bool {
    if dep, ok := pauseDeps[module]; ok {
     pause(dep)
    }
    return false
   })
  want := []string{
   `encountered dependency cycle`,
   `module "G" depends on module "F"`,
   `module "F" depends on module "G"`,
  }
  for i := range want {
   if len(errs) <= i {
    t.Errorf("missing error %s", want[i])
   } else if !strings.Contains(errs[i].Error(), want[i]) {
    t.Errorf("expected error %s, got %s", want[i], errs[i])
   }
  }
  if len(errs) > len(want) {
   for _, err := range errs[len(want):] {
    t.Errorf("unexpected error %s", err.Error())
   }
  }
 })
 t.Run("existing cycle", func(t *testing.T) {
  errs := parallelVisit(slices.Values([]*moduleInfo{moduleG, moduleH, moduleI, moduleJ}), bottomUpVisitorImpl{}, 3,
   func(module *moduleInfo, pause pauseFunc) bool {
    if module == moduleG {
     // Pause module G on module I.  This verifies the fix for b/433694465, where
     // having a module paused on a cycle of existing dependencies could drop
     // the cycle error.
     pause(moduleI)
    }
    return false
   })
  want := []string{
   `encountered dependency cycle`,
   `module "J" depends on module "I"`,
   `module "I" depends on module "J"`,
  }
  for i := range want {
   if len(errs) <= i {
    t.Errorf("missing error %s", want[i])
   } else if !strings.Contains(errs[i].Error(), want[i]) {
    t.Errorf("expected error %s, got %s", want[i], errs[i])
   }
  }
  if len(errs) > len(want) {
   for _, err := range errs[len(want):] {
    t.Errorf("unexpected error %s", err.Error())
   }
  }
 })
}

func TestDeduplicateOrderOnlyDeps(t *testing.T) {
 b := func(output string, inputs []string, orderOnlyDeps []string) *buildDef {
  return &buildDef{
   OutputStrings:    []string{output},
   InputStrings:     inputs,
   OrderOnlyStrings: uniquelist.Make(orderOnlyDeps),
  }
 }

 type testcase struct {
  bp             string
  expectedPhonys []*buildDef
  conversions    map[string][]string
 }
 fnvHash := func(s string) string {
  hash := fnv.New64a()
  hash.Write([]byte(s))
  return strconv.FormatUint(hash.Sum64(), 16)
 }
 testCases := []testcase{{
  bp: `
   foo_module {
     name: "A",
     outputs: ["A"],
     order_only: ["d"],
   }
   foo_module {
     name: "B",
     outputs: ["B"],
     order_only: ["d"],
   }
  `,
  expectedPhonys: []*buildDef{
   b("dedup-"+fnvHash("d"), []string{"d"}, nil),
  },
  conversions: map[string][]string{
   "A": []string{"dedup-" + fnvHash("d")},
   "B": []string{"dedup-" + fnvHash("d")},
  },
 }, {
  bp: `
   foo_module {
     name: "A",
     outputs: ["A"],
     order_only: ["a"],
   }
   foo_module {
     name: "B",
     outputs: ["B"],
     order_only: ["b"],
   }
  `,
 }, {
  bp: `
   foo_module {
     name: "A",
     outputs: ["A"],
     order_only: ["a"],
   }
   foo_module {
     name: "B",
     outputs: ["B"],
     order_only: ["b"],
   }
   foo_module {
     name: "C",
     outputs: ["C"],
     order_only: ["a"],
   }
  `,
  expectedPhonys: []*buildDef{b("dedup-"+fnvHash("a"), []string{"a"}, nil)},
  conversions: map[string][]string{
   "A": []string{"dedup-" + fnvHash("a")},
   "B": []string{"b"},
   "C": []string{"dedup-" + fnvHash("a")},
  },
 }, {
  bp: `
   foo_module {
     name: "A",
     outputs: ["A"],
     order_only: ["a", "b"],
     extra_outputs: ["B"],
     extra_order_only: ["a", "b"],
   }
   foo_module {
     name: "C",
     outputs: ["C"],
     order_only: ["a", "c"],
     extra_outputs: ["D"],
     extra_order_only: ["a", "c"],
   }
  `,
  expectedPhonys: []*buildDef{
   b("dedup-"+fnvHash("ab"), []string{"a", "b"}, nil),
   b("dedup-"+fnvHash("ac"), []string{"a", "c"}, nil)},
  conversions: map[string][]string{
   "A": []string{"dedup-" + fnvHash("ab")},
   "B": []string{"dedup-" + fnvHash("ab")},
   "C": []string{"dedup-" + fnvHash("ac")},
   "D": []string{"dedup-" + fnvHash("ac")},
  },
 }}
 for index, tc := range testCases {
  t.Run(fmt.Sprintf("TestCase-%d", index), func(t *testing.T) {
   ctx := bpSetup(t, tc.bp)
   _, errs := ctx.PrepareBuildActions(nil)
   if len(errs) > 0 {
    t.Errorf("unexpected errors calling generateModuleBuildActions:")
    for _, err := range errs {
     t.Errorf("  %s", err)
    }
    t.FailNow()
   }
   var modules []*moduleInfo
   for module := range ctx.iterateAllVariants() {
    modules = append(modules, module)
   }
   actualPhonys := ctx.deduplicateOrderOnlyDeps(modules)
   if len(actualPhonys.variables) != 0 {
    t.Errorf("No variables expected but found %v", actualPhonys.variables)
   }
   if len(actualPhonys.rules) != 0 {
    t.Errorf("No rules expected but found %v", actualPhonys.rules)
   }
   if e, a := len(tc.expectedPhonys), len(actualPhonys.buildDefs); e != a {
    t.Errorf("Expected %d build statements but got %d", e, a)
   }
   for i := 0; i < len(tc.expectedPhonys); i++ {
    a := actualPhonys.buildDefs[i]
    e := tc.expectedPhonys[i]
    if !reflect.DeepEqual(e.Outputs, a.Outputs) {
     t.Errorf("phonys expected %v but actualPhonys %v", e.Outputs, a.Outputs)
    }
    if !reflect.DeepEqual(e.Inputs, a.Inputs) {
     t.Errorf("phonys expected %v but actualPhonys %v", e.Inputs, a.Inputs)
    }
   }
   find := func(k string) *buildDef {
    for _, m := range modules {
     for _, b := range m.actionDefs.buildDefs {
      if reflect.DeepEqual(b.OutputStrings, []string{k}) {
       return b
      }
     }
    }
    return nil
   }
   for k, conversion := range tc.conversions {
    actual := find(k)
    if actual == nil {
     t.Errorf("Couldn't find %s", k)
    }
    if !reflect.DeepEqual(actual.OrderOnlyStrings.ToSlice(), conversion) {
     t.Errorf("expected %s.OrderOnly = %v but got %v", k, conversion, actual.OrderOnly)
    }
   }
  })
 }
}

func TestSourceRootDirAllowed(t *testing.T) {
 type pathCase struct {
  path           string
  decidingPrefix string
  allowed        bool
 }
 testcases := []struct {
  desc      string
  rootDirs  []string
  pathCases []pathCase
 }{
  {
   desc: "simple case",
   rootDirs: []string{
    "a",
    "b/c/d",
    "-c",
    "-d/c/a",
    "c/some_single_file",
   },
   pathCases: []pathCase{
    {
     path:           "a",
     decidingPrefix: "a",
     allowed:        true,
    },
    {
     path:           "a/b/c",
     decidingPrefix: "a",
     allowed:        true,
    },
    {
     path:           "b",
     decidingPrefix: "",
     allowed:        true,
    },
    {
     path:           "b/c/d/a",
     decidingPrefix: "b/c/d",
     allowed:        true,
    },
    {
     path:           "c",
     decidingPrefix: "c",
     allowed:        false,
    },
    {
     path:           "c/a/b",
     decidingPrefix: "c",
     allowed:        false,
    },
    {
     path:           "c/some_single_file",
     decidingPrefix: "c/some_single_file",
     allowed:        true,
    },
    {
     path:           "d/c/a/abc",
     decidingPrefix: "d/c/a",
     allowed:        false,
    },
   },
  },
  {
   desc: "root directory order matters",
   rootDirs: []string{
    "-a",
    "a/c/some_allowed_file",
    "a/b/d/some_allowed_file",
    "a/b",
    "a/c",
    "-a/b/d",
   },
   pathCases: []pathCase{
    {
     path:           "a",
     decidingPrefix: "a",
     allowed:        false,
    },
    {
     path:           "a/some_disallowed_file",
     decidingPrefix: "a",
     allowed:        false,
    },
    {
     path:           "a/c/some_allowed_file",
     decidingPrefix: "a/c/some_allowed_file",
     allowed:        true,
    },
    {
     path:           "a/b/d/some_allowed_file",
     decidingPrefix: "a/b/d/some_allowed_file",
     allowed:        true,
    },
    {
     path:           "a/b/c",
     decidingPrefix: "a/b",
     allowed:        true,
    },
    {
     path:           "a/b/c/some_allowed_file",
     decidingPrefix: "a/b",
     allowed:        true,
    },
    {
     path:           "a/b/d",
     decidingPrefix: "a/b/d",
     allowed:        false,
    },
   },
  },
 }
 for _, tc := range testcases {
  dirs := SourceRootDirs{}
  dirs.Add(tc.rootDirs...)
  for _, pc := range tc.pathCases {
   t.Run(fmt.Sprintf("%s: %s", tc.desc, pc.path), func(t *testing.T) {
    allowed, decidingPrefix := dirs.SourceRootDirAllowed(pc.path)
    if allowed != pc.allowed {
     if pc.allowed {
      t.Errorf("expected path %q to be allowed, but was not; root allowlist: %q", pc.path, tc.rootDirs)
     } else {
      t.Errorf("path %q was allowed unexpectedly; root allowlist: %q", pc.path, tc.rootDirs)
     }
    }
    if decidingPrefix != pc.decidingPrefix {
     t.Errorf("expected decidingPrefix to be %q, but got %q", pc.decidingPrefix, decidingPrefix)
    }
   })
  }
 }
}

func TestSourceRootDirs(t *testing.T) {
 root_foo_bp := `
 foo_module {
  name: "foo",
  deps: ["foo_dir1", "foo_dir_ignored_special_case"],
 }
 `
 dir1_foo_bp := `
 foo_module {
  name: "foo_dir1",
  deps: ["foo_dir_ignored"],
 }
 `
 dir_ignored_foo_bp := `
 foo_module {
  name: "foo_dir_ignored",
 }
 `
 dir_ignored_special_case_foo_bp := `
 foo_module {
  name: "foo_dir_ignored_special_case",
 }
 `
 mockFs := map[string][]byte{
  "Android.bp":                          []byte(root_foo_bp),
  "dir1/Android.bp":                     []byte(dir1_foo_bp),
  "dir_ignored/Android.bp":              []byte(dir_ignored_foo_bp),
  "dir_ignored/special_case/Android.bp": []byte(dir_ignored_special_case_foo_bp),
 }
 fileList := []string{}
 for f := range mockFs {
  fileList = append(fileList, f)
 }
 testCases := []struct {
  sourceRootDirs       []string
  expectedModuleDefs   []string
  unexpectedModuleDefs []string
  expectedErrs         []string
 }{
  {
   sourceRootDirs: []string{},
   expectedModuleDefs: []string{
    "foo",
    "foo_dir1",
    "foo_dir_ignored",
    "foo_dir_ignored_special_case",
   },
  },
  {
   sourceRootDirs: []string{"-", ""},
   unexpectedModuleDefs: []string{
    "foo",
    "foo_dir1",
    "foo_dir_ignored",
    "foo_dir_ignored_special_case",
   },
  },
  {
   sourceRootDirs: []string{"-"},
   unexpectedModuleDefs: []string{
    "foo",
    "foo_dir1",
    "foo_dir_ignored",
    "foo_dir_ignored_special_case",
   },
  },
  {
   sourceRootDirs: []string{"dir1"},
   expectedModuleDefs: []string{
    "foo",
    "foo_dir1",
    "foo_dir_ignored",
    "foo_dir_ignored_special_case",
   },
  },
  {
   sourceRootDirs: []string{"-dir1"},
   expectedModuleDefs: []string{
    "foo",
    "foo_dir_ignored",
    "foo_dir_ignored_special_case",
   },
   unexpectedModuleDefs: []string{
    "foo_dir1",
   },
   expectedErrs: []string{
    `Android.bp:2:2: module "foo" depends on skipped module "foo_dir1"; "foo_dir1" was defined in files(s) [dir1/Android.bp], but was skipped for reason(s) ["dir1/Android.bp" is a descendant of "dir1", and that path prefix was not included in PRODUCT_SOURCE_ROOT_DIRS]`,
   },
  },
  {
   sourceRootDirs: []string{"-", "dir1"},
   expectedModuleDefs: []string{
    "foo_dir1",
   },
   unexpectedModuleDefs: []string{
    "foo",
    "foo_dir_ignored",
    "foo_dir_ignored_special_case",
   },
   expectedErrs: []string{
    `dir1/Android.bp:2:2: module "foo_dir1" depends on skipped module "foo_dir_ignored"; "foo_dir_ignored" was defined in files(s) [dir_ignored/Android.bp], but was skipped for reason(s) ["dir_ignored/Android.bp" is a descendant of "", and that path prefix was not included in PRODUCT_SOURCE_ROOT_DIRS]`,
   },
  },
  {
   sourceRootDirs: []string{"-", "dir1", "dir_ignored/special_case/Android.bp"},
   expectedModuleDefs: []string{
    "foo_dir1",
    "foo_dir_ignored_special_case",
   },
   unexpectedModuleDefs: []string{
    "foo",
    "foo_dir_ignored",
   },
   expectedErrs: []string{
    "dir1/Android.bp:2:2: module \"foo_dir1\" depends on skipped module \"foo_dir_ignored\"; \"foo_dir_ignored\" was defined in files(s) [dir_ignored/Android.bp], but was skipped for reason(s) [\"dir_ignored/Android.bp\" is a descendant of \"\", and that path prefix was not included in PRODUCT_SOURCE_ROOT_DIRS]",
   },
  },
 }
 for _, tc := range testCases {
  t.Run(fmt.Sprintf(`source root dirs are %q`, tc.sourceRootDirs), func(t *testing.T) {
   ctx := NewContext()
   ctx.MockFileSystem(mockFs)
   ctx.RegisterModuleType("foo_module", newFooModule)
   ctx.RegisterBottomUpMutator("deps", depsMutator)
   ctx.AddSourceRootDirs(tc.sourceRootDirs...)
   ctx.ParseFileList(".", fileList, nil)
   _, actualErrs := ctx.ResolveDependencies(nil)

   stringErrs := []string(nil)
   for _, err := range actualErrs {
    stringErrs = append(stringErrs, err.Error())
   }
   if !reflect.DeepEqual(tc.expectedErrs, stringErrs) {
    t.Errorf("expected to find errors %v; got %v", tc.expectedErrs, stringErrs)
   }
   for _, modName := range tc.expectedModuleDefs {
    allMods := ctx.moduleGroupFromName(modName, nil)
    if allMods == nil || len(allMods.modules) != 1 {
     mods := moduleList{}
     if allMods != nil {
      mods = allMods.modules
     }
     t.Errorf("expected to find one definition for module %q, but got %v", modName, mods)
    }
   }

   for _, modName := range tc.unexpectedModuleDefs {
    allMods := ctx.moduleGroupFromName(modName, nil)
    if allMods != nil {
     t.Errorf("expected to find no definitions for module %q, but got %v", modName, allMods.modules)
    }
   }
  })
 }
}

func TestDisallowedMutatorMethods(t *testing.T) {
 testCases := []struct {
  name              string
  mutatorHandleFunc func(MutatorHandle)
  mutatorFunc       func(BottomUpMutatorContext)
  expectedPanic     string
 }{
  {
   name:              "rename",
   mutatorHandleFunc: func(handle MutatorHandle) { handle.UsesRename() },
   mutatorFunc:       func(ctx BottomUpMutatorContext) { ctx.Rename("qux") },
   expectedPanic:     "method Rename called from mutator that was not marked UsesRename",
  },
  {
   name:              "replace_dependencies",
   mutatorHandleFunc: func(handle MutatorHandle) { handle.UsesReplaceDependencies() },
   mutatorFunc:       func(ctx BottomUpMutatorContext) { ctx.ReplaceDependencies("bar") },
   expectedPanic:     "method ReplaceDependenciesIf called from mutator that was not marked UsesReplaceDependencies",
  },
  {
   name:              "replace_dependencies_if",
   mutatorHandleFunc: func(handle MutatorHandle) { handle.UsesReplaceDependencies() },
   mutatorFunc: func(ctx BottomUpMutatorContext) {
    ctx.ReplaceDependenciesIf("bar", func(from Module, tag DependencyTag, to Module) bool { return false })
   },
   expectedPanic: "method ReplaceDependenciesIf called from mutator that was not marked UsesReplaceDependencies",
  },
  {
   name:              "reverse_dependencies",
   mutatorHandleFunc: func(handle MutatorHandle) { handle.UsesReverseDependencies() },
   mutatorFunc:       func(ctx BottomUpMutatorContext) { ctx.AddReverseDependency(ctx.Module(), nil, "baz") },
   expectedPanic:     "method AddReverseDependency called from mutator that was not marked UsesReverseDependencies",
  },
  {
   name:              "create_module",
   mutatorHandleFunc: func(handle MutatorHandle) { handle.UsesCreateModule() },
   mutatorFunc: func(ctx BottomUpMutatorContext) {
    ctx.CreateModule(newFooModule, "create_module",
     &struct{ Name string }{Name: "quz"})
   },
   expectedPanic: "method CreateModule called from mutator that was not marked UsesCreateModule",
  },
 }

 runTest := func(mutatorHandleFunc func(MutatorHandle), mutatorFunc func(ctx BottomUpMutatorContext), expectedPanic string) {
  ctx := NewContext()

  ctx.MockFileSystem(map[string][]byte{
   "Android.bp": []byte(`
   foo_module {
    name: "foo",
   }

   foo_module {
    name: "bar",
    deps: ["foo"],
   }

   foo_module {
    name: "baz",
   }
  `)})

  ctx.RegisterModuleType("foo_module", newFooModule)
  ctx.RegisterBottomUpMutator("deps", depsMutator)
  handle := ctx.RegisterBottomUpMutator("mutator", func(ctx BottomUpMutatorContext) {
   if ctx.ModuleName() == "foo" {
    mutatorFunc(ctx)
   }
  })
  mutatorHandleFunc(handle)

  _, errs := ctx.ParseBlueprintsFiles("Android.bp", nil)
  if len(errs) > 0 {
   t.Errorf("unexpected parse errors:")
   for _, err := range errs {
    t.Errorf("  %s", err)
   }
   t.FailNow()
  }

  _, errs = ctx.ResolveDependencies(nil)
  if expectedPanic != "" {
   if len(errs) == 0 {
    t.Errorf("missing expected error %q", expectedPanic)
   } else if !strings.Contains(errs[0].Error(), expectedPanic) {
    t.Errorf("missing expected error %q in %q", expectedPanic, errs[0].Error())
   }
  } else if len(errs) > 0 {
   t.Errorf("unexpected dep errors:")
   for _, err := range errs {
    t.Errorf("  %s", err)
   }
   t.FailNow()
  }
 }

 noopMutatorHandleFunc := func(MutatorHandle) {}

 for _, testCase := range testCases {
  t.Run(testCase.name, func(t *testing.T) {
   t.Run("allowed", func(t *testing.T) {
    // Test that the method doesn't panic when the handle function is called.
    runTest(testCase.mutatorHandleFunc, testCase.mutatorFunc, "")
   })
   t.Run("disallowed", func(t *testing.T) {
    // Test that the method does panic with the expected error when the
    // handle function is not called.
    runTest(noopMutatorHandleFunc, testCase.mutatorFunc, testCase.expectedPanic)
   })
  })
 }

}

func Benchmark_parallelVisit(b *testing.B) {
 b.ReportAllocs()
 create := func(name string) *moduleInfo {
  m := &moduleInfo{
   group: &moduleGroup{
    name: name,
   },
  }
  m.group.modules = moduleList{m}
  return m
 }

 addDep := func(from, to *moduleInfo) {
  from.directDeps = append(from.directDeps, depInfo{to, nil})
  from.forwardDeps = append(from.forwardDeps, to)
  to.reverseDeps = append(to.reverseDeps, from)
 }
 _ = addDep

 var modules []*moduleInfo

 for i := range b.N {
  modules = append(modules, create(strconv.Itoa(i)))
  if i != 0 {
   //addDep(modules[len(modules)-1], modules[len(modules)-2])
  }
 }

 b.ResetTimer()
 errs := parallelVisit(slices.Values(modules), bottomUpVisitorImpl{}, 1000,
  func(module *moduleInfo, pause pauseFunc) bool {
   //fmt.Println(module.group.name)
   return false
  })
 if errs != nil {
  b.Errorf("expected no errors, got %q", errs)
 }
}

[Dauer der Verarbeitung: 0.30 Sekunden, vorverarbeitet 2026-06-28]