Skip to content

Commit

Permalink
gcts: implement async wait for checkout
Browse files Browse the repository at this point in the history
  • Loading branch information
palubaj authored and jfilak committed Sep 25, 2023
1 parent 0ad0f3a commit 96b4600
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 12 deletions.
3 changes: 2 additions & 1 deletion doc/commands/gcts.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,11 @@ sapcli gcts clone [--vsid VSID] [--starting-folder FOLDER] [--role ROLE] [--type
Checkout branch

```bash
sapcli gcts checkout [--format HUMAN|JSON] PACKAGE BRANCH
sapcli gcts checkout [--format HUMAN|JSON] [--wait-for-ready SECONDS] PACKAGE BRANCH
```

**Parameters:**:
- `--wait-for-ready SECONDS`: Wait for the checkout to finish (repository has switched branch)
- `--format`: Output format. The JSON format is particularly useful for automations because it contains Transport Request number.
- `PACKAGE`: Repository name or URL
- `BRANCH`: Name of the branch to checkout
Expand Down
35 changes: 32 additions & 3 deletions sap/cli/gcts.py
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,7 @@ def delete(connection, args):


@CommandGroup.argument('-f', '--format', choices=['HUMAN', 'JSON'], default='HUMAN')
@CommandGroup.argument('--wait-for-ready', type=int, nargs='?', default=0)
@CommandGroup.argument('--heartbeat', type=int, nargs='?', default=0)
@CommandGroup.argument('branch')
@CommandGroup.argument('package')
Expand All @@ -741,14 +742,42 @@ def checkout(connection, args):
console = sap.cli.core.get_console()
repo = get_repository(connection, args.package)
old_branch = repo.branch
with sap.cli.helpers.ConsoleHeartBeat(console, args.heartbeat):
response = sap.rest.gcts.simple.checkout(connection, args.branch, repo=repo)
from_commit = repo.head
try:
with sap.cli.helpers.ConsoleHeartBeat(console, args.heartbeat):
response = sap.rest.gcts.simple.checkout(connection, args.branch, repo=repo)

except HTTPRequestError as exc:
if args.wait_for_ready > 0:
repo = get_repository(connection, args.package)

console.printout('Checkout request responded with an error. Checking checkout process ...')
checkout_rc = get_activity_rc(repo, RepoActivitiesQueryParams.Operation.BRANCH_SW)
if checkout_rc != Repository.ActivityReturnCode.BRANCH_SW_SUCCES.value:
console.printerr(f'Checkout process failed with return code: {checkout_rc}!')
console.printerr(str(exc))
return 1

console.printout('Checkout process finished successfully. Waiting for repository to be ready ...')
with sap.cli.helpers.ConsoleHeartBeat(console, args.heartbeat):
def is_checkout_done(repo: Repository):
return repo.branch == args.branch

sap.rest.gcts.simple.wait_for_operation(repo, is_checkout_done, args.wait_for_ready, exc)

else:
console.printout('Checkout request responded with an error. Checkout "--wait-for-ready" parameter!')
console.printerr(str(exc))
return 1
else:
repo.wipe_data()

to_commit = repo.head
if args.format.upper() == 'JSON':
console.printout(sap.cli.core.json_dumps(response))
else:
console.printout(f'The repository "{repo.rid}" has been set to the branch "{args.branch}"')
console.printout(f'({old_branch}:{response["fromCommit"]}) -> ({args.branch}:{response["toCommit"]})')
console.printout(f'({old_branch}:{from_commit}) -> ({args.branch}:{to_commit})')
return 0


Expand Down
1 change: 1 addition & 0 deletions sap/rest/gcts/remote_repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ class ActivityReturnCode(Enum):
"""Repository activity return codes"""

CLONE_SUCCESS = 4
BRANCH_SW_SUCCES = 0

def __init__(self, connection, rid, data=None):
self._http = _RepositoryHttpProxy(connection, rid)
Expand Down
120 changes: 112 additions & 8 deletions test/unit/test_sap_cli_gcts.py
Original file line number Diff line number Diff line change
Expand Up @@ -540,29 +540,46 @@ def setUp(self):

self.patch_console(console=self.console)
self.fake_simple_checkout = self.patch('sap.rest.gcts.simple.checkout')
self.fake_repository = self.patch('sap.cli.gcts.Repository')
self.repo = Mock()
self.repo.rid = 'repo-id'
self.repo.name = 'the_repo'
self.fake_repository.return_value = self.repo
self.repo.branch = 'old_branch'

def checkout(self, *args, **kwargs):
return parse_args('checkout', *args, **kwargs)

def test_checkout_no_params(self):
@patch('sap.cli.gcts.Repository')
def test_checkout_no_params(self, fake_repo_class):
fake_repo_class.return_value = self.repo
conn = Mock()
self.repo.branch = 'old_branch'
self.fake_simple_checkout.return_value = {'fromCommit': '123', 'toCommit': '456'}

repo_from_commit = '123'
repo_to_commit = '456'

def fake_wipe():
self.repo.head = repo_to_commit

self.repo.wipe_data = fake_wipe
self.repo.head = repo_from_commit

args = self.checkout('the_repo', 'the_branch')
args.execute(conn, args)

self.fake_simple_checkout.assert_called_once_with(conn, 'the_branch', repo=self.repo)
self.assertConsoleContents(self.console, stdout=
f'''The repository "{self.repo.rid}" has been set to the branch "the_branch"
(old_branch:123) -> (the_branch:456)
(old_branch:{repo_from_commit}) -> (the_branch:{repo_to_commit})
''')

@staticmethod
def set_fake_wipe(repo, from_commit, to_commit):
get_head = Mock(side_effect=[from_commit, to_commit])

def fake_wipe():
repo.head = get_head()

repo.wipe_data = fake_wipe

@patch('sap.rest.gcts.simple.fetch_repos')
def test_checkout_with_url(self, fake_fetch_repos):
conn = Mock()
Expand All @@ -572,9 +589,12 @@ def test_checkout_with_url(self, fake_fetch_repos):
repo_name = 'the_repo'
repo_branch = 'old_branch'
repo_url = 'http://github.com/the_repo.git'
repo_from_commit = '123'
repo_to_commit = '456'
fake_repo = mock_repository(fake_fetch_repos, rid=repo_rid, name=repo_name, branch=repo_branch, url=repo_url)
self.set_fake_wipe(fake_repo, repo_from_commit, repo_to_commit)

self.fake_simple_checkout.return_value = {'fromCommit': '123', 'toCommit': '456'}
self.fake_simple_checkout.return_value = {'fromCommit': repo_from_commit, 'toCommit': repo_to_commit}
args = self.checkout(repo_url, checkout_branch)
args.execute(conn, args)

Expand Down Expand Up @@ -606,8 +626,10 @@ def test_checkout_json_output(self, fake_fetch_repos):
}
''')

@patch('sap.cli.gcts.Repository')
@patch('sap.cli.gcts.dump_gcts_messages')
def test_checkout_error(self, fake_dumper):
def test_checkout_error(self, fake_dumper, fake_repo_class):
fake_repo_class.return_value = self.repo
messages = {'exception': 'test'}
self.fake_simple_checkout.side_effect = sap.rest.gcts.errors.GCTSRequestError(messages)

Expand All @@ -627,6 +649,88 @@ def test_checkout_url_error(self, _):

self.assertConsoleContents(self.console, stderr=f'No repository found with the URL "{repo_url}".\n')

@patch('sap.rest.gcts.simple.fetch_repos')
def test_checkout_with_http_error(self, fake_fetch_repos):
conn = Mock()
checkout_branch = 'the_branch'

repo_rid = 'repo-id'
repo_name = 'the_repo'
repo_branch = 'old_branch'
repo_url = 'http://github.com/the_repo.git'
fake_repo = mock_repository(fake_fetch_repos, rid=repo_rid, name=repo_name, branch=repo_branch, url=repo_url)

self.fake_simple_checkout.side_effect = HTTPRequestError(None, Mock(text='Checkout exception', status_code=500))
args = self.checkout(repo_url, checkout_branch)
args.execute(conn, args)

self.fake_simple_checkout.assert_called_once_with(conn, checkout_branch, repo=fake_repo)
self.assertConsoleContents(self.console, stdout='Checkout request responded with an error. Checkout "--wait-for-ready" parameter!\n',
stderr='500\nCheckout exception\n')

@patch('sap.rest.gcts.simple.wait_for_operation')
@patch('sap.cli.gcts.get_activity_rc')
@patch('sap.rest.gcts.simple.fetch_repos')
def test_checkout_with_http_error_wait(self, fake_fetch_repos, fake_get_activity_rc, fake_wait_for_operation):
conn = Mock()
checkout_branch = 'the_branch'

repo_rid = 'repo-id'
repo_name = 'the_repo'
repo_branch = 'old_branch'
repo_url = 'http://github.com/the_repo.git'
fake_repo = mock_repository(fake_fetch_repos, rid=repo_rid, name=repo_name, branch=repo_branch, url=repo_url)
repo_from_commit = '123'
repo_to_commit = '456'
self.set_fake_wipe(fake_repo, repo_from_commit, repo_to_commit)

def get_activity_side_effect(repo, _):
repo.branch = checkout_branch
return 0

fake_get_activity_rc.side_effect = get_activity_side_effect

self.fake_simple_checkout.side_effect = HTTPRequestError(None, Mock(text='Checkout exception', status_code=500))
args = self.checkout(repo_url, checkout_branch, '--wait-for-ready', '10')
args.execute(conn, args)

checkout_test = fake_wait_for_operation.mock_calls[0].args[1]
self.assertTrue(checkout_test(fake_repo))
self.fake_simple_checkout.assert_called_once_with(conn, checkout_branch, repo=fake_repo)
self.assertConsoleContents(self.console, stdout=f'''Checkout request responded with an error. Checking checkout process ...
Checkout process finished successfully. Waiting for repository to be ready ...
The repository "{repo_rid}" has been set to the branch "{checkout_branch}"
({repo_branch}:{repo_from_commit}) -> ({checkout_branch}:{repo_to_commit})
''')

@patch('sap.rest.gcts.simple.wait_for_operation')
@patch('sap.cli.gcts.get_activity_rc')
@patch('sap.rest.gcts.simple.fetch_repos')
def test_checkout_with_http_error_wait_error(self, fake_fetch_repos, fake_get_activity_rc, fake_wait_for_operation):
conn = Mock()
checkout_branch = 'the_branch'

repo_rid = 'repo-id'
repo_name = 'the_repo'
repo_branch = 'old_branch'
repo_url = 'http://github.com/the_repo.git'
fake_repo = mock_repository(fake_fetch_repos, rid=repo_rid, name=repo_name, branch=repo_branch, url=repo_url)
fake_get_activity_rc.return_value = 1
repo_from_commit = '123'
repo_to_commit = '456'
self.set_fake_wipe(fake_repo, repo_from_commit, repo_to_commit)

self.fake_simple_checkout.side_effect = HTTPRequestError(None, Mock(text='Checkout exception', status_code=500))
args = self.checkout(repo_url, checkout_branch, '--wait-for-ready', '10')
args.execute(conn, args)

self.fake_simple_checkout.assert_called_once_with(conn, checkout_branch, repo=fake_repo)
self.assertConsoleContents(self.console, stdout='Checkout request responded with an error. Checking checkout process ...\n',
stderr='''Checkout process failed with return code: 1!
500
Checkout exception
''')


class TestgCTSLog(PatcherTestCase, ConsoleOutputTestCase):

Expand Down

0 comments on commit 96b4600

Please sign in to comment.