[cdc_rsync] Add integration tests (#42)

[cdc_rsync] Add integration tests

This CL adds Python integration tests for cdc_rsync. To run the tests,
you need to supply a Linux host and proper configuration for cdc_rsync
to work:

  set CDC_SSH_COMMAND=C:\path\to\ssh.exe <args>
  set CDC_SCP_COMMAND=C:\path\to\scp.exe <args>
  C:\python38\python.exe -m integration_tests.cdc_rsync.all_tests --binary_path=C:\full\path\to\cdc_rsync.exe --user_host=user@host

Ran the tests and made sure they worked.
This commit is contained in:
Lutz Justen
2022-12-08 08:39:43 +01:00
committed by GitHub
parent d2b594a41d
commit 668c2ca8df
15 changed files with 2151 additions and 0 deletions

View File

@@ -0,0 +1,243 @@
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Lint as: python3
"""cdc_rsync output test."""
import json
from integration_tests.framework import utils
from integration_tests.cdc_rsync import test_base
class OutputTest(test_base.CdcRsyncTest):
"""cdc_rsync output test class."""
def test_plain(self):
"""Runs rsync and verifies the total progress.
1) Uploads a file, verifies that the total progress is shown.
2) Uploads an empty folder with -r --delete options.
Verifies that the total delete messages are shown.
"""
utils.create_test_file(self.local_data_path, 1024)
res = utils.run_rsync(self.local_data_path, self.remote_base_dir)
self._assert_rsync_success(res)
self.assertIn('100% TOT', str(res.stdout))
utils.remove_test_file(self.local_data_path)
res = utils.run_rsync(self.local_base_dir, self.remote_base_dir, '-r',
'--delete')
self._assert_rsync_success(res)
self.assertIn('1/1 file(s) and 0/0 folder(s) deleted', str(res.stdout))
def test_verbose_1(self):
"""Runs rsync with -v option for multiple files.
1) Uploads 3 files with -v.
Verifies that each file is listed in the output as C100%.
2) Modifies 3 files, uploads them again with --v.
Verifies that each file is listed in the output as D100%.
3) Uploads an empty folder with -r --delete options.
Verifies that the delete messages are shown.
"""
files = ['file1. txt', 'file2.txt', 'file3.txt']
for file in files:
utils.create_test_file(self.local_base_dir + file, 1024)
res = utils.run_rsync(self.local_base_dir, self.remote_base_dir, '-v', '-r')
self._assert_rsync_success(res)
self.assertEqual(3, str(res.stdout).count('C100%'))
for file in files:
utils.create_test_file(self.local_base_dir + file, 2048)
res = utils.run_rsync(self.local_base_dir, self.remote_base_dir, '-v', '-r')
self._assert_rsync_success(res)
self.assertEqual(3, str(res.stdout).count('D100%'))
for file in files:
utils.remove_test_file(self.local_base_dir + file)
res = utils.run_rsync(self.local_base_dir, self.remote_base_dir, '-r',
'--delete')
self._assert_rsync_success(res)
self.assertIn('will be deleted due to --delete', str(res.stdout))
self.assertIn('3/3 file(s) and 0/0 folder(s) deleted', str(res.stdout))
def test_verbose_2(self):
"""Runs rsync with -vv option.
1) Uploads a file with -vv.
2) Verifies that additional logs show up.
"""
utils.create_test_file(self.local_data_path, 1024)
res = utils.run_rsync(self.local_data_path, self.remote_base_dir, '-vv')
self._assert_rsync_success(res)
output = str(res.stdout)
# client-side output
self._assert_regex('Starting process', output)
self._assert_not_regex(
r'process\.cc\([0-9]+\): Start\(\): Starting process', output)
# server-side output
self._assert_regex(
'INFO Finding all files in destination folder '
f"'{self.remote_base_dir}'", output)
self.assertNotIn('DEBUG', output)
def test_verbose_3(self):
"""Runs rsync with -vvv option.
1) Uploads a file with -vvv.
Verifies that additional logs show up (LOG_DEBUG logs).
2) Uploads a file to /invalid with -vvv.
Verifies that error messages including filenames are shown.
"""
utils.create_test_file(self.local_data_path, 1024)
res = utils.run_rsync(self.local_data_path, self.remote_base_dir, '-vvv')
self._assert_rsync_success(res)
output = str(res.stdout)
# client-side output
self._assert_regex(
r'cdc_rsync_client\.cc\([0-9]+\): SendOptions\(\): Sending options',
output)
# server-side output
self._assert_regex(
r'DEBUG server_socket\.cc\([0-9]+\): Receive\(\): EOF\(\) detected',
output)
# TODO: Add a check here, as currently the output is misleading
# res = utils.run_rsync(self.local_data_path, '/invalid', '-vvv')
def test_verbose_4(self):
"""Runs rsync with -vvv option.
1) Uploads a file with -vvvv.
2) Verifies that additional logs show up (LOG_VERBOSE logs).
"""
utils.create_test_file(self.local_data_path, 1024)
res = utils.run_rsync(self.local_data_path, self.remote_base_dir, '-vvvv')
self._assert_rsync_success(res)
output = str(res.stdout)
# client-side output
self._assert_regex(
r'message_pump\.cc\([0-9]+\): ThreadDoSendPacket\(\): Sent packet of size',
output)
# server-side output
self._assert_regex(
r'VERBOSE message_pump\.cc\([0-9]+\): ThreadDoReceivePacket\(\): Received packet of size',
output)
def test_quiet(self):
"""Runs rsync with -q option.
1) Uploads a file with -q.
2) Verifies that no output is shown.
"""
utils.create_test_file(self.local_data_path, 1024)
res = utils.run_rsync(self.local_data_path, self.remote_base_dir, '-q')
self._assert_rsync_success(res)
self.assertEqual('\r\n', res.stdout)
def test_quiet_error(self):
"""Runs rsync with -q option still showing errors.
1) Uploads a file with -q and bad options.
2) Verifies that an error message is shown.
"""
utils.create_test_file(self.local_data_path, 1024)
res = utils.run_rsync(self.local_data_path, self.remote_base_dir, '-q',
'-t')
self.assertEqual(res.returncode, 1)
self.assertEqual('\r\n', str(res.stdout))
self.assertIn('Unknown option: \'t\'', str(res.stderr))
# TODO: Add a test case for the non-existing destination.
def test_existing_verbose_1(self):
"""Runs rsync with -v --existing."""
files = ['file1.txt', 'file2.txt']
for file in files:
utils.create_test_file(self.local_base_dir + file, 1024)
res = utils.run_rsync(self.local_base_dir, self.remote_base_dir, '-r')
self._assert_rsync_success(res)
files.append('file3.txt')
for file in files:
utils.create_test_file(self.local_base_dir + file, 2048)
res = utils.run_rsync(self.local_base_dir, self.remote_base_dir, '-v', '-r',
'--existing')
self._assert_rsync_success(res)
output = str(res.stdout)
self.assertEqual(2, output.count('D100%'))
self.assertNotIn('file3.txt', output)
def test_json_per_file(self):
"""Runs rsync with -v --json."""
local_path = self.local_base_dir + 'test.txt'
utils.create_test_file(local_path, 1024)
res = utils.run_rsync(local_path, self.remote_base_dir, '-v', '--json')
self._assert_rsync_success(res)
output = str(res.stdout)
for val in self.parse_json(output):
self.assertEqual(val['file'], 'test.txt')
self.assertEqual(val['operation'], 'Copy')
self.assertEqual(val['size'], 1024)
# Those are actually all floats, but sometimes they get rounded to ints.
self.assertTrue(self.is_float_or_int(val['bytes_per_second']))
self.assertTrue(self.is_float_or_int(val['duration']))
self.assertTrue(self.is_float_or_int(val['eta']))
self.assertTrue(self.is_float_or_int(val['total_duration']))
self.assertTrue(self.is_float_or_int(val['total_eta']))
self.assertTrue(self.is_float_or_int(val['total_progress']))
def test_json_total(self):
"""Runs rsync with --json."""
local_path = self.local_base_dir + 'test.txt'
utils.create_test_file(local_path, 1024)
res = utils.run_rsync(local_path, self.remote_base_dir, '--json')
self._assert_rsync_success(res)
output = str(res.stdout)
for val in self.parse_json(output):
self.assertNotIn('file', val)
# Those are actually all floats, but sometimes they get rounded to ints.
self.assertTrue(self.is_float_or_int(val['total_duration']))
self.assertTrue(self.is_float_or_int(val['total_eta']))
self.assertTrue(self.is_float_or_int(val['total_progress']))
def parse_json(self, output):
"""Parses the JSON lines of output."""
lines = output.split('\r\n')
json_values = []
for line in lines:
if str.startswith(line, '{'):
json_values.append(json.loads(line.strip()))
return json_values
def is_float_or_int(self, val):
"""Returns true if val is a float or an int."""
return isinstance(val, float) or isinstance(val, int)
if __name__ == '__main__':
test_base.test_base.main()