diff --git a/tests/test_cli.py b/tests/test_cli.py index 72c0c19..ca872b2 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -228,3 +228,62 @@ def test_verbosity(tmpdir): ) assert result.exception assert 'invalid verbosity value' in result.output.lower() + + +def test_fail_fast(tmpdir): + runner = CliRunner() + config_file = tmpdir.join('config') + config_file.write(dedent(''' + [general] + status_path = {status} + processes = 1 + + [storage a1] + type = filesystem + fileext = .txt + path = {a1} + + [storage a2] + type = filesystem + fileext = .txt + path = {a2} + create = False + + [storage b1] + type = filesystem + fileext = .txt + path = {b1} + + [storage b2] + type = filesystem + fileext = .txt + path = {b2} + + [pair a] + a = a1 + b = a2 + + [pair b] + a = b1 + b = b2 + ''').format( + status=str(tmpdir.mkdir('status')), + a1=str(tmpdir.mkdir('a1')), + a2=str(tmpdir.join('a2')), + b1=str(tmpdir.mkdir('b1')), + b2=str(tmpdir.mkdir('b2')) + )) + + result = runner.invoke(cli.app, ['sync', 'a', 'b'], + env={'VDIRSYNCER_CONFIG': str(config_file)}) + lines = result.output.splitlines() + assert 'Syncing a' in lines + assert 'Syncing b' in lines + assert result.exception + + result = runner.invoke(cli.app, ['sync', '--fail-fast', 'a', 'b'], + env={'VDIRSYNCER_CONFIG': str(config_file)}) + lines = result.output.splitlines() + assert 'Syncing a' in lines + assert 'Syncing b' not in lines + assert result.exception diff --git a/vdirsyncer/cli.py b/vdirsyncer/cli.py index 22a10fd..87f53e4 100644 --- a/vdirsyncer/cli.py +++ b/vdirsyncer/cli.py @@ -300,9 +300,11 @@ def _create_app(): @click.option('--force-delete', multiple=True, help=('Disable data-loss protection for the given pairs. ' 'Can be passed multiple times')) + @click.option('--fail-fast', is_flag=True, + help='Exit immediately on first error.') @click.pass_context @catch_errors - def sync(ctx, pairs, force_delete): + def sync(ctx, pairs, force_delete, fail_fast): ''' Synchronize the given pairs. If no pairs are given, all will be synchronized. @@ -359,6 +361,12 @@ def _create_app(): rv = p.imap_unordered(_sync_collection, actions) + if not fail_fast: + # exhaust iterator before calling all(...), which would return + # after it encounters a False item. + # In other words, all(rv) fails fast. + rv = list(rv) + if not all(rv): sys.exit(1)