Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added comprehensive test for determining the current project. #2945

Merged
merged 2 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions pkg/projectfile/projectfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,10 @@ func (p *Project) SetBranch(branch string) error {
}

// GetProjectFilePath returns the path to the project activestate.yaml
// It considers projects in the following order:
// 1. Environment variable (e.g. `state shell` sets one)
// 2. Working directory (i.e. walk up directory tree looking for activestate.yaml)
// 3. Fall back on default project
Comment on lines +761 to +764
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great to have.

func GetProjectFilePath() (string, error) {
defer profile.Measure("GetProjectFilePath", time.Now())
lookup := []func() (string, error){
Expand Down
133 changes: 85 additions & 48 deletions pkg/projectfile/projectfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/ActiveState/cli/internal/constants"
"github.com/ActiveState/cli/internal/environment"
"github.com/ActiveState/cli/internal/errs"
"github.com/ActiveState/cli/internal/fileutils"
"github.com/ActiveState/cli/internal/language"
"github.com/ActiveState/cli/internal/locale"
"github.com/ActiveState/cli/internal/osutils"
Expand Down Expand Up @@ -207,59 +208,95 @@ func TestSave(t *testing.T) {
os.Remove(tmpfile.Name())
}

// Call getProjectFilePath
func TestGetProjectFilePath(t *testing.T) {
Reset()

root, err := environment.GetRootPath()
assert.NoError(t, err, "Should detect root path")
cwd, err := osutils.Getwd()
assert.NoError(t, err, "Should fetch cwd")
defer os.Chdir(cwd) // restore
os.Chdir(filepath.Join(root, "pkg", "projectfile", "testdata"))

configPath, err := GetProjectFilePath()
require.Nil(t, err)
expectedPath := filepath.Join(root, "pkg", "projectfile", "testdata", constants.ConfigFileName)
assert.Equal(t, expectedPath, configPath, "Project path is properly detected")
os.Chdir(cwd) // restore

defer os.Unsetenv(constants.ProjectEnvVarName)

os.Setenv(constants.ProjectEnvVarName, "/some/path")
configPath, err = GetProjectFilePath()
errt := &ErrorNoProjectFromEnv{}
require.ErrorAs(t, err, &errt)

expectedPath = filepath.Join(root, "pkg", "projectfile", "testdata", constants.ConfigFileName)
os.Setenv(constants.ProjectEnvVarName, expectedPath)
configPath, err = GetProjectFilePath()
require.Nil(t, err)
assert.Equal(t, expectedPath, configPath, "Project path is properly detected using the ProjectEnvVarName")

os.Unsetenv(constants.ProjectEnvVarName)
currentDir, err := os.Getwd()
require.NoError(t, err)
defer os.Chdir(currentDir)

rootDir, err := fileutils.ResolvePath(fileutils.TempDirUnsafe())
assert.NoError(t, err)
defer os.RemoveAll(rootDir)

// First, set up a new project with a subproject.
projectDir := filepath.Join(rootDir, "project")
require.NoError(t, fileutils.Mkdir(projectDir))
projectYaml := filepath.Join(projectDir, constants.ConfigFileName)
require.NoError(t, fileutils.Touch(projectYaml))
subprojectDir := filepath.Join(projectDir, "subproject")
require.NoError(t, fileutils.Mkdir(subprojectDir))
subprojectYaml := filepath.Join(subprojectDir, constants.ConfigFileName)
require.NoError(t, fileutils.Mkdir(subprojectDir))
require.NoError(t, fileutils.Touch(subprojectYaml))

// Then set up a separate, default project.
defaultDir := filepath.Join(rootDir, "default")
require.NoError(t, fileutils.Mkdir(defaultDir))
defaultYaml := filepath.Join(defaultDir, constants.ConfigFileName)
require.NoError(t, fileutils.Touch(defaultYaml))
cfg, err := config.New()
require.NoError(t, err)
defer func() { require.NoError(t, cfg.Close()) }()
cfg.Set(constants.GlobalDefaultPrefname, "") // ensure it is unset
tmpDir, err := ioutil.TempDir("", "")
assert.NoError(t, err, "Should create temp dir")
defer os.RemoveAll(tmpDir)
os.Chdir(tmpDir)
_, err = GetProjectFilePath()
assert.Error(t, err, "GetProjectFilePath should fail")
cfg.Set(constants.GlobalDefaultPrefname, expectedPath)
configPath, err = GetProjectFilePath()
assert.NoError(t, err, "GetProjectFilePath should succeed")
assert.Equal(t, expectedPath, configPath, "Project path is properly detected using default path from config")

// The activestate.yaml for an activated project should be used no matter what.
defer os.Unsetenv(constants.ActivatedStateEnvVarName)
os.Setenv(constants.ActivatedStateEnvVarName, filepath.Dir(expectedPath))
configPath, err = GetProjectFilePath()
require.Nil(t, err)
assert.Equal(t, expectedPath, configPath, "Project path is properly detected using the ActivatedStateEnvVarName")
os.Unsetenv(constants.ActivatedStateEnvVarName)
cfg.Set(constants.GlobalDefaultPrefname, defaultDir)

// Now set up an empty directory.
emptyDir := filepath.Join(rootDir, "empty")
require.NoError(t, fileutils.Mkdir(emptyDir))

// Now change to the project directory and assert GetProjectFilePath() returns it over the
// default project.
require.NoError(t, os.Chdir(projectDir))
path, err := GetProjectFilePath()
assert.NoError(t, err)
assert.Equal(t, projectYaml, path)

// `state shell` sets an environment variable, so run `state shell` in this project and then
// change to the subproject directory. Assert GetProjectFilePath() still returns the parent
// project.
require.NoError(t, os.Setenv(constants.ActivatedStateEnvVarName, projectDir))
defer os.Unsetenv(constants.ProfileEnvVarName)
require.NoError(t, os.Chdir(subprojectDir))
path, err = GetProjectFilePath()
assert.NoError(t, err)
assert.Equal(t, projectYaml, path)

// If the project were to not exist, GetProjectFilePath() should return a typed error.
require.NoError(t, os.Setenv(constants.ActivatedStateEnvVarName, filepath.Join(rootDir, "does-not-exist")))
path, err = GetProjectFilePath()
errNoProjectFromEnv := &ErrorNoProjectFromEnv{}
assert.ErrorAs(t, err, &errNoProjectFromEnv)

// After exiting out of the shell, the environment variable is no longer set. Assert
// GetProjectFilePath() returns the subproject.
require.NoError(t, os.Unsetenv(constants.ActivatedStateEnvVarName))
path, err = GetProjectFilePath()
assert.NoError(t, err)
assert.Equal(t, subprojectYaml, path)

// If a project's subdirectory does not contain an activestate.yaml file, GetProjectFilePath()
// should walk up the tree until it finds one.
require.NoError(t, os.Remove(subprojectYaml))
path, err = GetProjectFilePath()
assert.NoError(t, err)
assert.Equal(t, projectYaml, path)

// Change to an empty directory and assert GetProjectFilePath() returns the default project.
require.NoError(t, os.Chdir(emptyDir))
path, err = GetProjectFilePath()
assert.NoError(t, err)
assert.Equal(t, defaultYaml, path)

// If the default project no longer exists, GetProjectFilePath() should return a typed error.
cfg.Set(constants.GlobalDefaultPrefname, filepath.Join(rootDir, "does-not-exist"))
path, err = GetProjectFilePath()
errNoDefaultProject := &ErrorNoDefaultProject{}
assert.ErrorAs(t, err, &errNoDefaultProject)

// If none of the above, GetProjectFilePath() should return a typed error.
cfg.Set(constants.GlobalDefaultPrefname, "")
path, err = GetProjectFilePath()
errNoProject := &ErrorNoProject{}
assert.ErrorAs(t, err, &errNoProject)
}

// TestGet the config
Expand Down
105 changes: 105 additions & 0 deletions test/integration/shell_int_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,111 @@ func (suite *ShellIntegrationTestSuite) TestPs1() {
cp.ExpectExitCode(0)
}

func (suite *ShellIntegrationTestSuite) TestProjectOrder() {
suite.OnlyRunForTags(tagsuite.Critical, tagsuite.Shell)
ts := e2e.New(suite.T(), false)
defer ts.Close()

// First, set up a new project with a subproject.
cp := ts.Spawn("checkout", "ActiveState-CLI/Perl-5.32", "project")
cp.Expect("Skipping runtime setup")
cp.Expect("Checked out project")
cp.ExpectExitCode(0)
projectDir := filepath.Join(ts.Dirs.Work, "project")

cp = ts.SpawnWithOpts(
e2e.OptArgs("checkout", "ActiveState-CLI/Perl-5.32", "subproject"),
e2e.OptWD(projectDir),
)
cp.Expect("Skipping runtime setup")
cp.Expect("Checked out project")
cp.ExpectExitCode(0)
subprojectDir := filepath.Join(projectDir, "subproject")

// Then set up a separate project and make it the default.
cp = ts.Spawn("checkout", "ActiveState-CLI/Perl-5.32", "default")
cp.Expect("Skipping runtime setup")
cp.Expect("Checked out project")
cp.ExpectExitCode(0)
defaultDir := filepath.Join(ts.Dirs.Work, "default")

cp = ts.SpawnWithOpts(
e2e.OptArgs("use"),
e2e.OptWD(defaultDir),
e2e.OptAppendEnv(constants.DisableRuntime+"=false"),
)
cp.Expect("Setting Up Runtime", e2e.RuntimeSourcingTimeoutOpt)
cp.Expect("Switched to project", e2e.RuntimeSourcingTimeoutOpt)
cp.Expect(defaultDir)
cp.ExpectExitCode(0)

// Now set up an empty directory.
emptyDir := filepath.Join(ts.Dirs.Work, "empty")
suite.Require().NoError(fileutils.Mkdir(emptyDir))

// Now change to the project directory and assert that project is used instead of the default
// project.
cp = ts.SpawnWithOpts(
e2e.OptArgs("refresh"),
e2e.OptWD(projectDir),
)
cp.Expect(projectDir)
cp.ExpectExitCode(0)

// Run `state shell` in this project, change to the subproject directory, and assert the parent
// project is used instead of the subproject.
cp = ts.SpawnWithOpts(
e2e.OptArgs("shell"),
e2e.OptWD(projectDir),
e2e.OptAppendEnv(constants.DisableRuntime+"=false"),
)
cp.Expect("Activated", e2e.RuntimeSourcingTimeoutOpt)
cp.Expect(projectDir)
cp.SendLine("cd subproject")
cp.SendLine("state refresh")
cp.Expect(projectDir) // not subprojectDir
cp.SendLine("exit")
cp.Expect("Deactivated")
cp.ExpectExit() // exit code varies depending on shell; just assert the shell exited

// After exiting the shell, assert the subproject is used instead of the parent project.
cp = ts.SpawnWithOpts(
e2e.OptArgs("refresh"),
e2e.OptWD(subprojectDir),
)
cp.Expect(subprojectDir)
cp.ExpectExitCode(0)

// If a project subdirectory does not contain an activestate.yaml file, assert the project that
// owns the subdirectory will be used.
nestedDir := filepath.Join(subprojectDir, "nested")
suite.Require().NoError(fileutils.Mkdir(nestedDir))
cp = ts.SpawnWithOpts(
e2e.OptArgs("refresh"),
e2e.OptWD(nestedDir),
)
cp.Expect(subprojectDir)
cp.ExpectExitCode(0)

// Change to an empty directory and assert the default project is used.
cp = ts.SpawnWithOpts(
e2e.OptArgs("refresh"),
e2e.OptWD(emptyDir),
)
cp.Expect(defaultDir)
cp.ExpectExitCode(0)

// If none of the above, assert an error.
cp = ts.Spawn("use", "reset", "-n")
cp.ExpectExitCode(0)

cp = ts.SpawnWithOpts(
e2e.OptArgs("refresh"),
e2e.OptWD(emptyDir),
)
cp.ExpectNotExitCode(0)
}

func TestShellIntegrationTestSuite(t *testing.T) {
suite.Run(t, new(ShellIntegrationTestSuite))
}
Loading