Circular dependency check

This commit is contained in:
Berger Eugene 2023-07-08 15:05:39 +03:00
parent 9fb6eaa132
commit 08ddae986e
5 changed files with 78 additions and 33 deletions

View File

@ -8,18 +8,13 @@ processes:
process2: process2:
command: "echo process2" command: "echo process2"
availability:
restart: "on_failure"
depends_on: depends_on:
process3: process1:
condition: process_completed_successfully condition: process_completed_successfully
process3: process3:
command: "echo process3" command: "echo process3"
availability: depends_on:
restart: "on_failure" process1:
backoff_seconds: 2 condition: process_completed_successfully
# depends_on:
# process1:
# condition: process_completed_successfully

View File

@ -0,0 +1,11 @@
version: "0.5"
processes:
process1:
command: "echo process1"
process3:
command: "echo process3"
depends_on:
process1:
condition: process_completed_successfully

View File

@ -130,28 +130,27 @@ func TestSystem_TestComposeChainExit(t *testing.T) {
}) })
} }
//func TestSystem_TestComposeCircular(t *testing.T) { func TestSystem_TestComposeCircular(t *testing.T) {
// fixture := filepath.Join("..", "..", "fixtures", "process-compose-circular.yaml") fixture1 := filepath.Join("..", "..", "fixtures-code", "process-compose-circular.yaml")
// t.Run(fixture, func(t *testing.T) { fixture2 := filepath.Join("..", "..", "fixtures-code", "process-compose-non-circular.yaml")
// project, err := loader.Load(&loader.LoaderOptions{ t.Run(fixture1, func(t *testing.T) {
// FileNames: []string{fixture}, _, err := loader.Load(&loader.LoaderOptions{
// }) FileNames: []string{fixture1},
// if err != nil { })
// t.Errorf(err.Error()) if err == nil {
// return t.Errorf("should fail on cirlcular dependency")
// } return
// runner, err := NewProjectRunner(project, []string{}, false) }
// if err != nil {
// t.Errorf(err.Error()) _, err = loader.Load(&loader.LoaderOptions{
// return FileNames: []string{fixture2},
// } })
// exitCode := runner.Run() if err != nil {
// want := 42 t.Errorf(err.Error())
// if want != exitCode { return
// t.Errorf("Project.Run() = %v, want %v", exitCode, want) }
// } })
// }) }
//}
func TestSystem_TestComposeScale(t *testing.T) { func TestSystem_TestComposeScale(t *testing.T) {
fixture := filepath.Join("..", "..", "fixtures-code", "process-compose-scale.yaml") fixture := filepath.Join("..", "..", "fixtures-code", "process-compose-scale.yaml")

View File

@ -27,7 +27,7 @@ func Load(opts *LoaderOptions) (*types.Project, error) {
opts.projects = append(opts.projects, p) opts.projects = append(opts.projects, p)
} }
mergedProject, err := merge(opts) mergedProject, err := merge(opts)
mergedProject.ValidateAfterMerge() err = mergedProject.ValidateAfterMerge()
return mergedProject, err return mergedProject, err
} }

View File

@ -1,6 +1,7 @@
package types package types
import ( import (
"fmt"
"github.com/f1bonacc1/process-compose/src/command" "github.com/f1bonacc1/process-compose/src/command"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@ -14,9 +15,10 @@ func (p *Project) Validate() {
p.validateProcessConfig() p.validateProcessConfig()
} }
func (p *Project) ValidateAfterMerge() { func (p *Project) ValidateAfterMerge() error {
p.assignDefaultProcessValues() p.assignDefaultProcessValues()
p.cloneReplicas() p.cloneReplicas()
return p.validateNoCircularDependencies()
} }
func (p *Project) validateLogLevel() { func (p *Project) validateLogLevel() {
@ -100,3 +102,41 @@ func (p *Project) cloneReplicas() {
p.Processes[proc.ReplicaName] = proc p.Processes[proc.ReplicaName] = proc
} }
} }
func (p *Project) validateNoCircularDependencies() error {
visited := make(map[string]bool, len(p.Processes))
stack := make(map[string]bool)
for name := range p.Processes {
if !visited[name] {
if p.isCyclicHelper(name, visited, stack) {
return fmt.Errorf("circular dependency found in %s", name)
}
}
}
return nil
}
func (p *Project) isCyclicHelper(procName string, visited map[string]bool, stack map[string]bool) bool {
visited[procName] = true
stack[procName] = true
processes, err := p.getProcesses(procName)
if err != nil {
return false
}
for _, process := range processes {
dependencies := process.GetDependencies()
for _, neighbor := range dependencies {
if !visited[neighbor] {
if p.isCyclicHelper(neighbor, visited, stack) {
return true
}
} else if stack[neighbor] {
return true
}
}
}
stack[procName] = false
return false
}