package state import ( "io/ioutil" "os" "path/filepath" "time" "github.com/glebarez/sqlite" "github.com/hashicorp/go-hclog" "github.com/hashicorp/vagrant-plugin-sdk/proto/vagrant_plugin_sdk" "github.com/hashicorp/vagrant/internal/server" "github.com/hashicorp/vagrant/internal/server/proto/vagrant_server" "github.com/imdario/mergo" "github.com/mitchellh/go-testing-interface" "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" "gorm.io/gorm" "gorm.io/gorm/logger" ) // TestState returns an initialized State for testing. func TestState(t testing.T) *State { t.Helper() var result *State t.Cleanup(func() { t.Log("test state cleanup for", t.Name()) result.Close() }) result, err := New( hclog.New(&hclog.LoggerOptions{ Name: "testing", Level: hclog.Trace, Output: os.Stdout, IncludeLocation: true, }), TestDB(t), ) require.NoError(t, err) return result } // TestStateReinit reinitializes the state by pretending to restart // the server with the database associated with this state. This can be // used to test index init logic. // // NOTE: The new state is created before the old one is closed so the // shared in memory database is reused (data retained) func TestStateReinit(t testing.T, s *State) *State { newState := TestState(t) require.NoError(t, s.Close()) return newState } func TestDB(t testing.T) *gorm.DB { t.Helper() db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{ Logger: logger.New( hclog.New(&hclog.LoggerOptions{ Name: "testing", Level: hclog.Warn, Output: os.Stdout, IncludeLocation: false, }).StandardLogger( &hclog.StandardLoggerOptions{ InferLevels: true, }), logger.Config{ SlowThreshold: 200 * time.Millisecond, LogLevel: logger.Warn, IgnoreRecordNotFoundError: false, Colorful: true, }, ), }) db.Exec("PRAGMA foreign_keys = ON") if err != nil { panic("failed to enable foreign key constraints: " + err.Error()) } if err := db.AutoMigrate(models...); err != nil { require.NoError(t, err) } t.Cleanup(func() { dbconn, err := db.DB() if err == nil { dbconn.Close() } }) return db } func RequireAndDB(t testing.T) (*require.Assertions, *gorm.DB) { db := TestDB(t) require := require.New(t) return require, db } func TestBasis(t testing.T, db *gorm.DB) *Basis { t.Helper() td := TestTempDir(t) b := &Basis{ Name: filepath.Base(td), Path: td, } result := db.Save(b) require.NoError(t, result.Error) return b } // TestBasis creates the basis in the DB. func TestBasisProto(t testing.T, s *State) *vagrant_plugin_sdk.Ref_Basis { t.Helper() return TestBasis(t, s.db).ToProtoRef() } func TestProject(t testing.T, db *gorm.DB) *Project { b := TestBasis(t, db) td := TestTempDir(t) p := &Project{ Name: filepath.Base(td), Path: td, Basis: b, } result := db.Save(p) require.NoError(t, result.Error) return p } func TestProjectProto(t testing.T, s *State) *vagrant_plugin_sdk.Ref_Project { t.Helper() return TestProject(t, s.db).ToProtoRef() } func testRunnerProto(t testing.T, s *State, src *vagrant_server.Runner) *vagrant_server.Runner { t.Helper() if src == nil { src = &vagrant_server.Runner{} } id, err := server.Id() require.NoError(t, err) base := &vagrant_server.Runner{Id: id} require.NoError(t, mergo.Merge(src, base)) var runner Runner require.NoError(t, s.decode(src, &runner)) result := s.db.Save(&runner) require.NoError(t, result.Error) return runner.ToProto() } func TestJobProto(t testing.T, src *vagrant_server.Job) *vagrant_server.Job { t.Helper() dst := &vagrant_server.Job{ TargetRunner: &vagrant_server.Ref_Runner{ Target: &vagrant_server.Ref_Runner_Any{ Any: &vagrant_server.Ref_RunnerAny{}, }, }, DataSource: &vagrant_server.Job_DataSource{ Source: &vagrant_server.Job_DataSource_Local{ Local: &vagrant_server.Job_Local{}, }, }, Operation: &vagrant_server.Job_Noop_{ Noop: &vagrant_server.Job_Noop{}, }, } proto.Merge(dst, src) return dst } func TestTempDir(t testing.T) string { t.Helper() dir, err := ioutil.TempDir("", "vagrant-test") require.NoError(t, err) t.Cleanup(func() { os.RemoveAll(dir) }) return dir }