asteris-llc/converge

View on GitHub
load/dependencyresolver_test.go

Summary

Maintainability
A
3 hrs
Test Coverage
// 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))
            }
        }
    })
}