github.com/bhameyie/otto@v0.2.1-0.20160406174117-16052efa52ec/appfile/compile_test.go (about)

     1  package appfile
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"os/exec"
     9  	"path/filepath"
    10  	"reflect"
    11  	"strings"
    12  	"testing"
    13  
    14  	"github.com/hashicorp/terraform/dag"
    15  )
    16  
    17  var testHasGit bool
    18  
    19  func init() {
    20  	if _, err := exec.LookPath("git"); err == nil {
    21  		testHasGit = true
    22  	}
    23  }
    24  
    25  func TestCompiled_impl(t *testing.T) {
    26  	var _ json.Marshaler = new(Compiled)
    27  	var _ json.Unmarshaler = new(Compiled)
    28  }
    29  
    30  func TestCompile(t *testing.T) {
    31  	cases := []struct {
    32  		Dir    string
    33  		String string
    34  		Err    bool
    35  	}{
    36  		{
    37  			"compile-basic",
    38  			testCompileBasicStr,
    39  			false,
    40  		},
    41  
    42  		{
    43  			"compile-deps",
    44  			testCompileDepsStr,
    45  			false,
    46  		},
    47  
    48  		{
    49  			"compile-multi-dep",
    50  			testCompileMultiDepStr,
    51  			false,
    52  		},
    53  
    54  		{
    55  			"compile-invalid",
    56  			"",
    57  			true,
    58  		},
    59  
    60  		{
    61  			"compile-cycle",
    62  			"",
    63  			true,
    64  		},
    65  
    66  		{
    67  			"compile-deps-no-id",
    68  			"",
    69  			true,
    70  		},
    71  
    72  		/*
    73  			TODO: uncomment once we can enforce this
    74  			{
    75  				"compile-diff-project",
    76  				"",
    77  				true,
    78  			},
    79  		*/
    80  	}
    81  
    82  	for _, tc := range cases {
    83  		t.Logf("Testing: %s", tc.Dir)
    84  
    85  		// We wrap this in a function just so we can use defers
    86  		func() {
    87  			opts := testCompileOpts(t)
    88  			defer os.RemoveAll(opts.Dir)
    89  			f := testFile(t, tc.Dir)
    90  			defer f.resetID()
    91  
    92  			c, err := testCompiler(t, opts).Compile(f)
    93  			if (err != nil) != tc.Err {
    94  				t.Fatalf("err: %s\n\n%s", tc.Dir, err)
    95  			}
    96  
    97  			if err == nil {
    98  				if tc.String != "" {
    99  					testCompileCompare(t, c, tc.String)
   100  				}
   101  				testCompileMarshal(t, c, opts.Dir)
   102  			}
   103  		}()
   104  	}
   105  }
   106  
   107  // This is a really important test case that verifies that ".ottoid"
   108  // is not ignored from dependencies. We had this happen with 0.1
   109  func TestCompile_dotOttoId(t *testing.T) {
   110  	if !testHasGit {
   111  		t.Log("git not found, skipping")
   112  		t.Skip()
   113  	}
   114  
   115  	opts := testCompileOpts(t)
   116  	defer os.RemoveAll(opts.Dir)
   117  	f := testFile(t, "compile-deps-git")
   118  	defer f.resetID()
   119  
   120  	// Rename DOTgit to .git since Git doesn't allow nested .git
   121  	dir := filepath.Join(filepath.Dir(f.Path), "child")
   122  	oldName := filepath.Join(dir, "DOTgit")
   123  	newName := filepath.Join(dir, ".git")
   124  	if err := os.Rename(oldName, newName); err != nil {
   125  		t.Fatalf("err: %s", err)
   126  	}
   127  	defer os.Rename(newName, oldName)
   128  
   129  	c, err := testCompiler(t, opts).Compile(f)
   130  	if err != nil {
   131  		t.Fatalf("err:\n\n%s", err)
   132  	}
   133  
   134  	testCompileCompare(t, c, testCompileDepGitStr)
   135  	testCompileMarshal(t, c, opts.Dir)
   136  }
   137  
   138  func TestCompile_structure(t *testing.T) {
   139  	cases := []struct {
   140  		Dir  string
   141  		Name string
   142  		File *File
   143  		Err  bool
   144  	}{
   145  		{
   146  			"import-basic",
   147  			"",
   148  			&File{
   149  				Application: &Application{
   150  					Name:   "foo",
   151  					Type:   "bar",
   152  					Detect: true,
   153  				},
   154  				Project: &Project{
   155  					Name:           "foo",
   156  					Infrastructure: "aws",
   157  				},
   158  				Infrastructure: []*Infrastructure{
   159  					&Infrastructure{
   160  						Name: "aws",
   161  						Type: "aws",
   162  					},
   163  				},
   164  			},
   165  			false,
   166  		},
   167  
   168  		{
   169  			"import-nested",
   170  			"",
   171  			&File{
   172  				Application: &Application{
   173  					Name:   "bar",
   174  					Type:   "bar",
   175  					Detect: true,
   176  				},
   177  				Project: &Project{
   178  					Name:           "foo",
   179  					Infrastructure: "aws",
   180  				},
   181  				Infrastructure: []*Infrastructure{
   182  					&Infrastructure{
   183  						Name: "aws",
   184  						Type: "aws",
   185  					},
   186  				},
   187  			},
   188  			false,
   189  		},
   190  
   191  		{
   192  			"import-cycle",
   193  			"",
   194  			nil,
   195  			true,
   196  		},
   197  
   198  		{
   199  			"import-dep",
   200  			"child",
   201  			&File{
   202  				Application: &Application{
   203  					Name:   "child",
   204  					Type:   "bar",
   205  					Detect: true,
   206  				},
   207  				Project: &Project{
   208  					Name:           "bar",
   209  					Infrastructure: "aws",
   210  				},
   211  				Infrastructure: []*Infrastructure{
   212  					&Infrastructure{
   213  						Name: "aws",
   214  						Type: "aws",
   215  					},
   216  				},
   217  			},
   218  			false,
   219  		},
   220  
   221  		{
   222  			"compile-dep-infra",
   223  			"child",
   224  			&File{
   225  				Application: &Application{
   226  					Name:   "child",
   227  					Type:   "bar",
   228  					Detect: true,
   229  				},
   230  				Project: &Project{
   231  					Name:           "bar",
   232  					Infrastructure: "google",
   233  				},
   234  				Infrastructure: []*Infrastructure{
   235  					&Infrastructure{
   236  						Name: "google",
   237  						Type: "google",
   238  					},
   239  				},
   240  			},
   241  			false,
   242  		},
   243  	}
   244  
   245  	for _, tc := range cases {
   246  		t.Logf("Testing: %s", tc.Dir)
   247  
   248  		// We wrap this in a function just so we can use defers
   249  		func() {
   250  			opts := testCompileOpts(t)
   251  			defer os.RemoveAll(opts.Dir)
   252  			f := testFile(t, tc.Dir)
   253  			f.initID()
   254  			f.loadID()
   255  			defer f.resetID()
   256  
   257  			if tc.File != nil {
   258  				tc.File.ID = f.ID
   259  				tc.File.Path = f.Path
   260  				tc.File.Imports = f.Imports
   261  			}
   262  
   263  			c, err := testCompiler(t, opts).Compile(f)
   264  			if (err != nil) != tc.Err {
   265  				t.Fatalf("err: %s\n\n%s", tc.Dir, err)
   266  			}
   267  			if err == nil && c == nil {
   268  				t.Fatalf("bad: compiled is nil\n\n%s", tc.Dir)
   269  			}
   270  			if err != nil {
   271  				return
   272  			}
   273  
   274  			// Get the root file. If we specified a name, then find
   275  			// the dependent application with that name since that is
   276  			// what we're comparing against.
   277  			actual := c.File
   278  			if tc.Name != "" {
   279  				actual = nil
   280  				c.Graph.Walk(func(raw dag.Vertex) error {
   281  					v := raw.(*CompiledGraphVertex)
   282  					if v.File.Application == nil {
   283  						return nil
   284  					}
   285  					if v.File.Application.Name == tc.Name {
   286  						actual = v.File
   287  					}
   288  					return nil
   289  				})
   290  
   291  				if actual == nil {
   292  					t.Fatalf("err: %s\n\n%s not found in graph", tc.Dir, tc.Name)
   293  				}
   294  
   295  				// For child files, we just clear these out.
   296  				actual.ID = ""
   297  				actual.Path = ""
   298  				actual.Source = ""
   299  				actual.Imports = nil
   300  				tc.File.ID = actual.ID
   301  				tc.File.Path = actual.Path
   302  				tc.File.Imports = actual.Imports
   303  				tc.File.Source = actual.Source
   304  			}
   305  
   306  			if !reflect.DeepEqual(actual, tc.File) {
   307  				t.Fatalf("err: %s\n\n%#v\n\n%#v", tc.Dir, actual, tc.File)
   308  			}
   309  		}()
   310  	}
   311  }
   312  
   313  func TestCompileID(t *testing.T) {
   314  	opts := testCompileOpts(t)
   315  	defer os.RemoveAll(opts.Dir)
   316  	f := testFile(t, "compile-id")
   317  	defer f.resetID()
   318  
   319  	c, err := testCompiler(t, opts).Compile(f)
   320  	if err != nil {
   321  		t.Fatalf("err: %s", err)
   322  	}
   323  
   324  	if f.ID == "" {
   325  		t.Fatalf("ID should not be blank")
   326  	}
   327  	if f.ID != c.File.ID {
   328  		t.Fatalf("%s != %s", f.ID, c.File.ID)
   329  	}
   330  }
   331  
   332  func TestCompileID_existing(t *testing.T) {
   333  	opts := testCompileOpts(t)
   334  	defer os.RemoveAll(opts.Dir)
   335  	f := testFile(t, "compile-id-exists")
   336  	if f.ID == "" {
   337  		t.Fatalf("ID should not be blank")
   338  	}
   339  
   340  	copyId := f.ID
   341  	c, err := testCompiler(t, opts).Compile(f)
   342  	if err != nil {
   343  		t.Fatalf("err: %s", err)
   344  	}
   345  
   346  	if copyId != c.File.ID {
   347  		t.Fatalf("%s != %s", f.ID, c.File.ID)
   348  	}
   349  }
   350  
   351  func TestLoadCompile_new(t *testing.T) {
   352  	path := filepath.Join("./test-fixtures", "load-new")
   353  	_, err := LoadCompiled(path)
   354  	if err == nil {
   355  		t.Fatal("should error")
   356  	}
   357  }
   358  
   359  func testCompileCompare(t *testing.T, c *Compiled, expected string) {
   360  	actual := strings.TrimSpace(c.String())
   361  	expected = strings.TrimSpace(fmt.Sprintf(expected, c.File.Path))
   362  	if actual != expected {
   363  		t.Fatalf("bad:\n\n%s\n\n%s", actual, expected)
   364  	}
   365  }
   366  
   367  func testCompileMarshal(t *testing.T, original *Compiled, dir string) {
   368  	c, err := LoadCompiled(dir)
   369  	if err != nil {
   370  		t.Fatalf("err loading compiled: %s", err)
   371  	}
   372  
   373  	if c.String() != original.String() {
   374  		t.Fatalf("bad:\n\n%s\n\n%s", c, original)
   375  	}
   376  }
   377  
   378  func testCompileOpts(t *testing.T) *CompileOpts {
   379  	dir, err := ioutil.TempDir("", "otto-")
   380  	if err != nil {
   381  		t.Fatalf("err: %s", err)
   382  	}
   383  
   384  	return &CompileOpts{
   385  		Dir:    dir,
   386  		Loader: nil,
   387  	}
   388  }
   389  
   390  func testCompiler(t *testing.T, opts *CompileOpts) *Compiler {
   391  	c, err := NewCompiler(opts)
   392  	if err != nil {
   393  		t.Fatalf("err: %s", err)
   394  	}
   395  
   396  	return c
   397  }
   398  
   399  func testFile(t *testing.T, dir string) *File {
   400  	path := filepath.Join("./test-fixtures", dir, "Appfile")
   401  	f, err := ParseFile(path)
   402  	if err != nil {
   403  		t.Fatalf("err: %s\n\n%s", path, err)
   404  	}
   405  
   406  	return f
   407  }
   408  
   409  const testCompileBasicStr = `
   410  Compiled Appfile: %s
   411  
   412  Dep Graph:
   413  foo
   414  `
   415  
   416  const testCompileDepsStr = `
   417  Compiled Appfile: %s
   418  
   419  Dep Graph:
   420  bar
   421  foo
   422    bar
   423  `
   424  
   425  const testCompileDepGitStr = `
   426  Compiled Appfile: %s
   427  
   428  Dep Graph:
   429  bar
   430  foo
   431    bar
   432  `
   433  
   434  const testCompileMultiDepStr = `
   435  Compiled Appfile: %s
   436  
   437  Dep Graph:
   438  bar
   439  baz
   440  foo
   441    bar
   442    baz
   443  `