Skip to content

Commit

Permalink
Merge pull request #2355 from posit-dev/sagerb-parse-r-version-from-c…
Browse files Browse the repository at this point in the history
…ommand-output-fix

R Version Detection: Loop over lines rather than just first line of 'R --version' output
  • Loading branch information
sagerb authored Oct 10, 2024
2 parents 7f86774 + a64f5ec commit 6a83702
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 66 deletions.
20 changes: 12 additions & 8 deletions internal/inspect/r.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,15 +192,19 @@ func (i *defaultRInspector) getRVersion(rExecutable string) (string, error) {
if err != nil {
return "", err
}
line := strings.SplitN(string(append(output, stderr...)), "\n", 2)[0]
m := rVersionRE.FindStringSubmatch(line)
if len(m) < 2 {
return "", fmt.Errorf("couldn't parse R version from output: %s", line)
lines := strings.SplitN(string(append(output, stderr...)), "\n", -1)
for _, l := range lines {
i.log.Info("Parsing line for R version", "l", l)
m := rVersionRE.FindStringSubmatch(l)
if len(m) < 2 {
continue
}
version := m[1]
i.log.Info("Detected R version", "version", version)
rVersionCache[rExecutable] = version
return version, nil
}
version := m[1]
i.log.Info("Detected R version", "version", version)
rVersionCache[rExecutable] = version
return version, nil
return "", fmt.Errorf("couldn't parse R version from command output (%s --version)", rExecutable)
}

var renvLockRE = regexp.MustCompile(`^\[1\] "(.*)"`)
Expand Down
157 changes: 99 additions & 58 deletions internal/inspect/r_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,14 @@ func (s *RSuite) TestNewRInspector() {
s.Equal(log, inspector.log)
}

const rOutput = `R version 4.3.0 (2023-04-21) -- "Already Tomorrow"
type OutputTestData struct {
output, expectedVersion string
}

func getOutputTestData() []OutputTestData {
data := []OutputTestData{
// typical output from command `r --version`
{`R version 4.3.0 (2023-04-21) -- "Already Tomorrow"
Copyright (C) 2023 The R Foundation for Statistical Computing
Platform: x86_64-apple-darwin20 (64-bit)
Expand All @@ -55,57 +62,89 @@ You are welcome to redistribute it under the terms of the
GNU General Public License versions 2 or 3.
For more information about these matters see
https://www.gnu.org/licenses/.
`
`, "4.3.0"},
// output when there is a warning
{`WARNING: ignoring environment value of R_HOME
R version 4.3.3 (2024-02-29) -- "Angel Food Cake"
Copyright (C) 2024 The R Foundation for Statistical Computing
Platform: x86_64-apple-darwin20 (64-bit)
func (s *RSuite) TestGetRVersionFromExecutable() {
log := logging.New()
rPath := s.cwd.Join("bin", "R")
rPath.Dir().MkdirAll(0777)
rPath.WriteFile(nil, 0777)
i := NewRInspector(s.cwd, rPath.Path, log)
inspector := i.(*defaultRInspector)
R is free software and comes with ABSOLUTELY NO WARRANTY.
You are welcome to redistribute it under the terms of the
GNU General Public License versions 2 or 3.
For more information about these matters see
https://www.gnu.org/licenses/.`, "4.3.3"},

// output when there are multiple warnings
// as well as closely matching version strings
{`WARNING: ignoring environment value of R_HOME
WARNING: your mom is calling
WARNING: time to stand
Somewhere below is the correct R version 4.3.* that we're looking for
R version 4.3.3 (2024-02-29) -- "Angel Food Cake"
Copyright (C) 2024 The R Foundation for Statistical Computing
Platform: x86_64-apple-darwin20 (64-bit)
executor := executortest.NewMockExecutor()
executor.On("RunCommand", rPath.String(), []string{"--version"}, mock.Anything, mock.Anything).Return([]byte(rOutput), nil, nil)
inspector.executor = executor
version, err := inspector.getRVersion(rPath.String())
s.NoError(err)
s.Equal("4.3.0", version)
}
R is free software and comes with ABSOLUTELY NO WARRANTY.
You are welcome to redistribute it under the terms of the
GNU General Public License versions 2 or 3.
For more information about these matters see
https://www.gnu.org/licenses/.`, "4.3.3"},

func (s *RSuite) TestGetRVersionFromExecutableWindows() {
// R on Windows emits version information on stderr
log := logging.New()
rPath := s.cwd.Join("bin", "R")
rPath.Dir().MkdirAll(0777)
rPath.WriteFile(nil, 0777)
i := NewRInspector(s.cwd, rPath.Path, log)
inspector := i.(*defaultRInspector)
// test output where version exists in multiple locations
// we want to get it from the first location
{`
R version 4.3.3 (2024-02-29) -- "Angel Food Cake"
Copyright (C) 2024 The R Foundation for Statistical Computing
Platform: x86_64-apple-darwin20 (64-bit)
R version 4.1.1 (2023-12-29) -- "Fantasy Island"
executor := executortest.NewMockExecutor()
executor.On("RunCommand", rPath.String(), []string{"--version"}, mock.Anything, mock.Anything).Return(nil, []byte(rOutput), nil)
inspector.executor = executor
version, err := inspector.getRVersion(rPath.String())
s.NoError(err)
s.Equal("4.3.0", version)
R is free software and comes with ABSOLUTELY NO WARRANTY.
You are welcome to redistribute it under the terms of the
GNU General Public License versions 2 or 3.
For more information about these matters see
https://www.gnu.org/licenses/.`, "4.3.3"},
}
return data
}

func (s *RSuite) TestGetRVersionFromExecutableErr() {
rPath := s.cwd.Join("bin", "R")
rPath.Dir().MkdirAll(0777)
rPath.WriteFile(nil, 0777)
log := logging.New()
i := NewRInspector(s.cwd, rPath.Path, log)
inspector := i.(*defaultRInspector)
func (s *RSuite) TestGetRVersionFromExecutable() {
for _, tc := range getOutputTestData() {
s.SetupTest()
log := logging.New()
rPath := s.cwd.Join("bin", "R")
rPath.Dir().MkdirAll(0777)
rPath.WriteFile(nil, 0777)
i := NewRInspector(s.cwd, rPath.Path, log)
inspector := i.(*defaultRInspector)

executor := executortest.NewMockExecutor()
executor.On("RunCommand", rPath.String(), []string{"--version"}, mock.Anything, mock.Anything).Return([]byte(tc.output), nil, nil)
inspector.executor = executor
version, err := inspector.getRVersion(rPath.String())
s.NoError(err)
s.Equal(tc.expectedVersion, version)
}
}

executor := executortest.NewMockExecutor()
testError := errors.New("test error from RunCommand")
executor.On("RunCommand", rPath.String(), []string{"--version"}, mock.Anything, mock.Anything).Return(nil, nil, testError)
inspector.executor = executor
version, err := inspector.getRVersion(rPath.String())
s.NotNil(err)
s.ErrorIs(err, testError)
s.Equal("", version)
func (s *RSuite) TestGetRVersionFromExecutableWindows() {
for _, tc := range getOutputTestData() {
s.SetupTest()
// R on Windows emits version information on stderr
log := logging.New()
rPath := s.cwd.Join("bin", "R")
rPath.Dir().MkdirAll(0777)
rPath.WriteFile(nil, 0777)
i := NewRInspector(s.cwd, rPath.Path, log)
inspector := i.(*defaultRInspector)

executor := executortest.NewMockExecutor()
executor.On("RunCommand", rPath.String(), []string{"--version"}, mock.Anything, mock.Anything).Return(nil, []byte(tc.output), nil)
inspector.executor = executor
version, err := inspector.getRVersion(rPath.String())
s.NoError(err)
s.Equal(tc.expectedVersion, version)
}
}

func (s *RSuite) TestGetRVersionFromRealDefaultR() {
Expand Down Expand Up @@ -238,20 +277,22 @@ func (s *RSuite) TestGetRVersionFromLockFile() {
}

func (s *RSuite) TestGetRExecutable() {
log := logging.New()
executor := executortest.NewMockExecutor()
executor.On("RunCommand", "/some/R", []string{"--version"}, mock.Anything, mock.Anything).Return([]byte(rOutput), nil, nil)
i := &defaultRInspector{
executor: executor,
log: log,
}
for _, tc := range getOutputTestData() {
log := logging.New()
executor := executortest.NewMockExecutor()
executor.On("RunCommand", "/some/R", []string{"--version"}, mock.Anything, mock.Anything).Return([]byte(tc.output), nil, nil)
i := &defaultRInspector{
executor: executor,
log: log,
}

pathLooker := util.NewMockPathLooker()
pathLooker.On("LookPath", "R").Return("/some/R", nil)
i.pathLooker = pathLooker
executable, err := i.getRExecutable()
s.NoError(err)
s.Equal("/some/R", executable)
pathLooker := util.NewMockPathLooker()
pathLooker.On("LookPath", "R").Return("/some/R", nil)
i.pathLooker = pathLooker
executable, err := i.getRExecutable()
s.NoError(err)
s.Equal("/some/R", executable)
}
}

func (s *RSuite) TestGetRExecutableSpecifiedR() {
Expand Down

0 comments on commit 6a83702

Please sign in to comment.