load/dependencyresolver_test.go
// Copyright © 2016 Asteris, LLC
//
// 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 load_test
import (
"context"
"testing"
"github.com/asteris-llc/converge/graph"
"github.com/asteris-llc/converge/helpers/logging"
"github.com/asteris-llc/converge/helpers/testing/graphutils"
"github.com/asteris-llc/converge/helpers/testing/hclutils"
"github.com/asteris-llc/converge/load"
"github.com/asteris-llc/converge/parse"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestDependencyResolverResolvesDependencies tests dependency resolution
func TestDependencyResolverResolvesDependencies(t *testing.T) {
defer logging.HideLogs(t)()
nodes, err := load.Nodes(context.Background(), "../samples/basicDependencies.hcl", false)
require.NoError(t, err)
resolved, err := load.ResolveDependencies(context.Background(), nodes)
assert.NoError(t, err)
assert.Contains(
t,
graph.Targets(resolved.DownEdges("root/task.render")),
"root/task.directory",
)
}
// TestDependencyResolverResolvesExplicitDepsInBranch tests explicit
// dependencies inside of case branch nodes
func TestDependencyResolverResolvesExplicitDepsInBranch(t *testing.T) {
defer logging.HideLogs(t)()
nodes, err := load.Nodes(context.Background(), "../samples/conditionalDeps.hcl", false)
require.NoError(t, err)
resolved, err := load.ResolveDependencies(context.Background(), nodes)
assert.NoError(t, err)
assert.Contains(
t,
graph.Targets(resolved.DownEdges("root/macro.switch.sample/macro.case.true/file.content.foo-output")),
"root/task.query.foo",
)
}
func TestDependencyResolverBadDependency(t *testing.T) {
defer logging.HideLogs(t)()
nodes, err := load.Nodes(context.Background(), "../samples/errors/bad_requirement.hcl", false)
require.NoError(t, err)
_, err = load.ResolveDependencies(context.Background(), nodes)
if assert.Error(t, err) {
assert.EqualError(t, err, "1 error(s) occurred:\n\n* root/task.bad_requirement: nonexistent vertices in edges: task.nonexistent")
}
}
func TestDependencyResolverResolvesParam(t *testing.T) {
defer logging.HideLogs(t)()
nodes, err := load.Nodes(context.Background(), "../samples/basicDependencies.hcl", false)
require.NoError(t, err)
resolved, err := load.ResolveDependencies(context.Background(), nodes)
assert.NoError(t, err)
assert.Contains(
t,
graph.Targets(resolved.DownEdges("root/task.directory")),
"root/param.filename",
)
assert.Contains(
t,
graph.Targets(resolved.DownEdges("root/task.render")),
"root/param.filename",
)
assert.Contains(
t,
graph.Targets(resolved.DownEdges("root/task.render")),
"root/param.message",
)
}
// TestDependencyResolverHandlesConditionalMetadata ensures that we generate
// dependencies for predicates
func TestDependencyResolverHandlesConditionalMetadata(t *testing.T) {
t.Parallel()
type test struct {
Value int
}
src := `
param "a" {
default = "a"
}
task.query "a" {
interpreter = "/bin/bash"
query = "echo -n a"
}
task.query "test-param" {
query = "echo test-param"
}
task.query "test-lookup" {
query = "echo test-lookup"
}
task.query "test-param-and-lookup" {
query = "echo test-param-andlookup"
}
task.query "test-none" {
query = "test-none"
}
`
gr, err := hclutils.LoadFromString("ResolverHandlesConditionalMetadata", src)
require.NoError(t, err)
node, ok := gr.Get("root/task.query.test-param")
require.True(t, ok)
node.AddMetadata("conditional-predicate-raw", "{{param `a`}}")
node, ok = gr.Get("root/task.query.test-lookup")
require.True(t, ok)
node.AddMetadata("conditional-predicate-raw", "{{lookup `task.query.a.value`}}")
node, ok = gr.Get("root/task.query.test-param-and-lookup")
require.True(t, ok)
node.AddMetadata("conditional-predicate-raw", "eq {{param `a`}} {{lookup `task.query.a.value`}}")
node, ok = gr.Get("root/task.query.test-none")
require.True(t, ok)
node.AddMetadata("conditional-predicate-raw", "true")
g, err := load.ResolveDependencies(context.Background(), gr)
require.NoError(t, err)
t.Run("params", func(t *testing.T) {
assert.True(t, graphutils.DependsOn(g, "root/task.query.test-param", "root/param.a"))
})
t.Run("lookups", func(t *testing.T) {
assert.True(t, graphutils.DependsOn(g, "root/task.query.test-lookup", "root/task.query.a"))
})
t.Run("params-and-lookups", func(t *testing.T) {
assert.True(t, graphutils.DependsOn(g, "root/task.query.test-param-and-lookup", "root/param.a"))
assert.True(t, graphutils.DependsOn(g, "root/task.query.test-param-and-lookup", "root/task.query.a"))
})
}
// TestDependencyResolverResolvesGroupDependencies tests whether group
// dependencies are wired correctly
func TestDependencyResolverResolvesGroupDependencies(t *testing.T) {
t.Parallel()
defer logging.HideLogs(t)()
t.Run("intra-module", func(t *testing.T) {
nodes, err := load.Nodes(context.Background(), "../samples/groups.hcl", false)
require.NoError(t, err)
resolved, err := load.ResolveDependencies(context.Background(), nodes)
assert.NoError(t, err)
group := "apt"
groupNodes := resolved.GroupNodes(group)
assert.NotEmpty(t, groupNodes)
for _, node := range groupNodes {
assert.True(t, len(resolved.DownEdgesInGroup(node.ID, group)) <= 1)
assert.True(t, len(resolved.UpEdgesInGroup(node.ID, group)) <= 1)
// find the highest node
if len(resolved.UpEdges(node.ID)) == 1 {
// it should depend on the other nodes
assert.Equal(t, 2, len(resolved.Dependencies(node.ID)))
}
}
})
t.Run("inter-module", func(t *testing.T) {
nodes, err := load.Nodes(context.Background(), "../samples/groupedIncludeModule.hcl", false)
require.NoError(t, err)
resolved, err := load.ResolveDependencies(context.Background(), nodes)
assert.NoError(t, err)
group := "groupedModule"
groupNodes := resolved.GroupNodes(group)
assert.NotEmpty(t, groupNodes)
for _, node := range groupNodes {
moduleID := graph.ParentID(node.ID)
assert.True(t, len(resolved.DownEdgesInGroup(moduleID, group)) <= 1)
assert.True(t, len(resolved.UpEdgesInGroup(moduleID, group)) <= 1)
// find the highest node
if len(resolved.UpEdges(moduleID)) == 1 {
// it should depend on the other modules
var moduleDeps []string
for _, depID := range resolved.Dependencies(moduleID) {
if dep, ok := resolved.Get(depID); ok {
depNode, ok := dep.Value().(*parse.Node)
if ok && depNode.IsModule() {
moduleDeps = append(moduleDeps, dep.ID)
}
}
}
assert.Equal(t, 2, len(moduleDeps))
}
}
})
}