-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Improve pod install error message (#90)
* CocoapodsInstaller uses latest go-utils and go-steputils dependencies * Implement cocoapods command error finder * Test CocoapodsInstaller * Update go-utils and go-xcode * Improve error message for transient errors * Update error messages * Migrate CocoapodsInstaller to go-utils/v2/log * Rename cocoapodsinstaller to cocoapods_installer * findErrors if statement optimization * Test if first pod install fails * Cleanup pod installer tests
- Loading branch information
Showing
68 changed files
with
7,680 additions
and
362 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
package main | ||
|
||
import ( | ||
"bufio" | ||
"fmt" | ||
"os" | ||
"strings" | ||
|
||
"github.com/bitrise-io/go-steputils/v2/ruby" | ||
"github.com/bitrise-io/go-utils/v2/command" | ||
"github.com/bitrise-io/go-utils/v2/errorutil" | ||
"github.com/bitrise-io/go-utils/v2/log" | ||
) | ||
|
||
// CocoapodsInstaller ... | ||
type CocoapodsInstaller struct { | ||
rubyCmdFactory ruby.CommandFactory | ||
logger log.Logger | ||
} | ||
|
||
// NewCocoapodsInstaller ... | ||
func NewCocoapodsInstaller(rubyCmdFactory ruby.CommandFactory, logger log.Logger) CocoapodsInstaller { | ||
return CocoapodsInstaller{ | ||
rubyCmdFactory: rubyCmdFactory, | ||
logger: logger, | ||
} | ||
} | ||
|
||
// InstallPods ... | ||
func (i CocoapodsInstaller) InstallPods(podArg []string, podCmd string, podfileDir string, verbose bool) error { | ||
if err := i.runPodInstall(podArg, podCmd, podfileDir, verbose); err == nil { | ||
return nil | ||
} else { | ||
i.logger.Printf("") | ||
i.logger.Warnf(errorutil.FormattedError(fmt.Errorf("Failed to install Pods: %w", err))) | ||
i.logger.Warnf("Retrying with pod repo update...") | ||
i.logger.Printf("") | ||
} | ||
|
||
if err := i.runPodRepoUpdate(podArg, podfileDir, verbose); err != nil { | ||
return err | ||
} | ||
|
||
if err := i.runPodInstall(podArg, podCmd, podfileDir, verbose); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (i CocoapodsInstaller) runPodInstall(podArg []string, podCmd string, podfileDir string, verbose bool) error { | ||
errorFinder := &cocoapodsCmdErrorFinder{} | ||
cmdSlice := podInstallCmdSlice(podArg, podCmd, verbose) | ||
cmd := createPodCommand(i.rubyCmdFactory, cmdSlice, podfileDir, errorFinder) | ||
i.logger.Donef("$ %s", cmd.PrintableCommandArgs()) | ||
return cmd.Run() | ||
} | ||
|
||
func (i CocoapodsInstaller) runPodRepoUpdate(podArg []string, podfileDir string, verbose bool) error { | ||
errorFinder := &cocoapodsCmdErrorFinder{} | ||
cmdSlice := podRepoUpdateCmdSlice(podArg, verbose) | ||
cmd := createPodCommand(i.rubyCmdFactory, cmdSlice, podfileDir, errorFinder) | ||
i.logger.Donef("$ %s", cmd.PrintableCommandArgs()) | ||
return cmd.Run() | ||
} | ||
|
||
func podInstallCmdSlice(podArg []string, podCmd string, verbose bool) []string { | ||
cmdSlice := append(podArg, podCmd, "--no-repo-update") | ||
if verbose { | ||
cmdSlice = append(cmdSlice, "--verbose") | ||
} | ||
return cmdSlice | ||
} | ||
|
||
func podRepoUpdateCmdSlice(podArg []string, verbose bool) []string { | ||
cmdSlice := append(podArg, "repo", "update") | ||
if verbose { | ||
cmdSlice = append(cmdSlice, "--verbose") | ||
} | ||
return cmdSlice | ||
} | ||
|
||
func createPodCommand(factory ruby.CommandFactory, args []string, dir string, errorFinder *cocoapodsCmdErrorFinder) command.Command { | ||
return factory.Create(args[0], args[1:], &command.Opts{ | ||
Stdout: os.Stdout, | ||
Stderr: os.Stderr, | ||
Stdin: nil, | ||
Env: nil, | ||
Dir: dir, | ||
ErrorFinder: errorFinder.findErrors, | ||
}) | ||
} | ||
|
||
type cocoapodsCmdErrorFinder struct { | ||
transientProblemAlreadySeen bool | ||
} | ||
|
||
func (f *cocoapodsCmdErrorFinder) findErrors(out string) []string { | ||
var errors []string | ||
|
||
reader := strings.NewReader(out) | ||
scanner := bufio.NewScanner(reader) | ||
scanner.Split(bufio.ScanLines) | ||
for scanner.Scan() { | ||
line := scanner.Text() | ||
|
||
if strings.HasPrefix(line, "[!] ") || strings.HasPrefix(line, "curl: ") { | ||
errors = append(errors, line) | ||
} else if strings.HasPrefix(line, "Warning: Transient problem: ") { | ||
if !f.transientProblemAlreadySeen { | ||
errors = append(errors, "Transient problem") | ||
f.transientProblemAlreadySeen = true | ||
} | ||
} | ||
} | ||
if err := scanner.Err(); err != nil { | ||
return nil | ||
} | ||
|
||
return errors | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
package main | ||
|
||
import ( | ||
"errors" | ||
"strings" | ||
"testing" | ||
|
||
"bitrise-steplib/steps-cocoapods-install/mocks" | ||
|
||
"github.com/stretchr/testify/mock" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func Test_GivenCocoapodsInstaller_WhenArgsGiven_ThenRunsExpectedCommand(t *testing.T) { | ||
type args struct { | ||
podArg []string | ||
podCmd string | ||
verbose bool | ||
} | ||
tests := []struct { | ||
name string | ||
args args | ||
wantCmd []string | ||
}{ | ||
{ | ||
name: "simple pod install", | ||
args: args{podArg: []string{"pod"}, podCmd: "install", verbose: false}, | ||
wantCmd: []string{"pod", "install", "--no-repo-update"}, | ||
}, | ||
{ | ||
name: "verbose pod install", | ||
args: args{podArg: []string{"pod"}, podCmd: "install", verbose: true}, | ||
wantCmd: []string{"pod", "install", "--no-repo-update", "--verbose"}, | ||
}, | ||
{ | ||
name: "verbose pod update", | ||
args: args{podArg: []string{"pod"}, podCmd: "update", verbose: true}, | ||
wantCmd: []string{"pod", "update", "--no-repo-update", "--verbose"}, | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
// Given | ||
cmd := new(mocks.Command) | ||
cmd.On("PrintableCommandArgs").Return(strings.Join(tt.wantCmd, " ")) | ||
cmd.On("Run").Return(nil) | ||
|
||
cmdFactory := new(mocks.CommandFactory) | ||
cmdFactory.On("Create", tt.wantCmd[0], tt.wantCmd[1:], mock.Anything).Return(cmd) | ||
|
||
logger := new(mocks.Logger) | ||
logger.On("Donef", mock.Anything, mock.Anything) | ||
|
||
installer := NewCocoapodsInstaller(cmdFactory, logger) | ||
|
||
// When | ||
err := installer.InstallPods(tt.args.podArg, tt.args.podCmd, "", tt.args.verbose) | ||
|
||
// Then | ||
require.NoError(t, err) | ||
cmdFactory.AssertExpectations(t) | ||
cmd.AssertExpectations(t) | ||
}) | ||
} | ||
} | ||
|
||
func Test_GivenCocoapodsInstaller_WhenInstallFails_ThenRunsRepoUpdateAndRetries(t *testing.T) { | ||
// Given | ||
podArg := []string{"pod"} | ||
podCmd := "install" | ||
|
||
firstInstallCmd := new(mocks.Command) | ||
firstInstallCmd.On("PrintableCommandArgs").Return(mock.Anything) | ||
firstInstallCmd.On("Run").Return(errors.New("[!] Error installing boost")).Once() | ||
|
||
repoUpdateCmd := new(mocks.Command) | ||
repoUpdateCmd.On("PrintableCommandArgs").Return(mock.Anything) | ||
repoUpdateCmd.On("Run").Return(nil).Once() | ||
|
||
secondInstallCmd := new(mocks.Command) | ||
secondInstallCmd.On("PrintableCommandArgs").Return(mock.Anything) | ||
secondInstallCmd.On("Run").Return(nil).Once() | ||
|
||
cmdFactory := new(mocks.CommandFactory) | ||
cmdFactory.On("Create", podArg[0], []string{podCmd, "--no-repo-update"}, mock.Anything).Return(firstInstallCmd).Once() | ||
cmdFactory.On("Create", podArg[0], []string{"repo", "update"}, mock.Anything).Return(repoUpdateCmd).Once() | ||
cmdFactory.On("Create", podArg[0], []string{podCmd, "--no-repo-update"}, mock.Anything).Return(secondInstallCmd).Once() | ||
|
||
logger := new(mocks.Logger) | ||
logger.On("Donef", mock.Anything, mock.Anything) | ||
logger.On("Printf", mock.Anything, mock.Anything) | ||
logger.On("Warnf", mock.Anything, mock.Anything) | ||
|
||
installer := NewCocoapodsInstaller(cmdFactory, logger) | ||
|
||
// When | ||
err := installer.InstallPods(podArg, podCmd, "", false) | ||
|
||
// Then | ||
require.NoError(t, err) | ||
cmdFactory.AssertExpectations(t) | ||
firstInstallCmd.AssertExpectations(t) | ||
repoUpdateCmd.AssertExpectations(t) | ||
secondInstallCmd.AssertExpectations(t) | ||
} | ||
|
||
func Test_GivenCocoapodsErrorFinder_WhenGatewayTimeOut_ThenFindsErrors(t *testing.T) { | ||
expectedErrors := []string{ | ||
"[!] Error installing boost", | ||
"[!] /usr/bin/curl -f -L -o /var/folders/v9/hjkgcpmn6bq99p7gvyhpq6800000gn/T/d20221018-7204-3bfs7/file.tbz https://boostorg.jfrog.io/artifactory/main/release/1.76.0/source/boost_1_76_0.tar.bz2 --create-dirs --netrc-optional --retry 2 -A 'CocoaPods/1.11.3 cocoapods-downloader/1.6.3'", | ||
"Transient problem", | ||
"curl: (22) The requested URL returned error: 504 Gateway Time-out", | ||
} | ||
errorFinder := cocoapodsCmdErrorFinder{} | ||
errors := errorFinder.findErrors(podInstallGatewayTimeOutError) | ||
require.Equal(t, expectedErrors, errors) | ||
} | ||
|
||
func Test_GivenCocoapodsErrorFinder_WhenBadGateway_ThenFindsErrors(t *testing.T) { | ||
expectedErrors := []string{ | ||
"[!] Error installing boost", | ||
"[!] /usr/bin/curl -f -L -o /var/folders/v9/hjkgcpmn6bq99p7gvyhpq6800000gn/T/d20221018-3650-smj60t/file.tbz https://boostorg.jfrog.io/artifactory/main/release/1.76.0/source/boost_1_76_0.tar.bz2 --create-dirs --netrc-optional --retry 2 -A 'CocoaPods/1.11.3 cocoapods-downloader/1.6.3'", | ||
"Transient problem", | ||
"curl: (22) The requested URL returned error: 502 Bad Gateway", | ||
} | ||
errorFinder := cocoapodsCmdErrorFinder{} | ||
errors := errorFinder.findErrors(podInstallBadGatewayError) | ||
require.Equal(t, expectedErrors, errors) | ||
} | ||
|
||
const podInstallGatewayTimeOutError = `Installing YogaKit (1.18.1) | ||
Installing boost (1.76.0) | ||
[!] Error installing boost | ||
[!] /usr/bin/curl -f -L -o /var/folders/v9/hjkgcpmn6bq99p7gvyhpq6800000gn/T/d20221018-7204-3bfs7/file.tbz https://boostorg.jfrog.io/artifactory/main/release/1.76.0/source/boost_1_76_0.tar.bz2 --create-dirs --netrc-optional --retry 2 -A 'CocoaPods/1.11.3 cocoapods-downloader/1.6.3' | ||
% Total % Received % Xferd Average Speed Time Time Time Current | ||
Dload Upload Total Spent Left Speed | ||
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 | ||
0 0 0 0 0 0 0 0 --:--:-- 0:00:01 --:--:-- 0 | ||
0 0 0 0 0 0 0 0 --:--:-- 0:00:01 --:--:-- 0 | ||
Warning: Transient problem: HTTP error Will retry in 1 seconds. 2 retries | ||
Warning: left. | ||
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 | ||
0 0 0 0 0 0 0 0 --:--:-- 0:00:01 --:--:-- 0 | ||
0 0 0 0 0 0 0 0 --:--:-- 0:01:15 --:--:-- 0 | ||
Warning: Transient problem: HTTP error Will retry in 2 seconds. 1 retries | ||
Warning: left. | ||
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 | ||
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 | ||
0 0 0 0 0 0 0 0 --:--:-- 0:00:01 --:--:-- 0 | ||
0 0 0 0 0 0 0 0 --:--:-- 0:01:15 --:--:-- 0 | ||
curl: (22) The requested URL returned error: 504 Gateway Time-out | ||
` | ||
|
||
const podInstallBadGatewayError = `Installing YogaKit (1.18.1) | ||
Installing boost (1.76.0) | ||
[!] Error installing boost | ||
[!] /usr/bin/curl -f -L -o /var/folders/v9/hjkgcpmn6bq99p7gvyhpq6800000gn/T/d20221018-3650-smj60t/file.tbz https://boostorg.jfrog.io/artifactory/main/release/1.76.0/source/boost_1_76_0.tar.bz2 --create-dirs --netrc-optional --retry 2 -A 'CocoaPods/1.11.3 cocoapods-downloader/1.6.3' | ||
% Total % Received % Xferd Average Speed Time Time Time Current | ||
Dload Upload Total Spent Left Speed | ||
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 | ||
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 | ||
0 0 0 0 0 0 0 0 --:--:-- 0:00:01 --:--:-- 0 | ||
0 0 0 0 0 0 0 0 --:--:-- 0:01:05 --:--:-- 0 | ||
Warning: Transient problem: HTTP error Will retry in 1 seconds. 2 retries | ||
Warning: left. | ||
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 | ||
0 0 0 0 0 0 0 0 --:--:-- 0:00:01 --:--:-- 0 | ||
0 0 0 0 0 0 0 0 --:--:-- 0:01:15 --:--:-- 0 | ||
Warning: Transient problem: HTTP error Will retry in 2 seconds. 1 retries | ||
Warning: left. | ||
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 | ||
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 | ||
0 0 0 0 0 0 0 0 --:--:-- 0:00:01 --:--:-- 0 | ||
curl: (22) The requested URL returned error: 502 Bad Gateway` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.