|  | @@ -318,8 +318,6 @@ class ArchiverTestCaseBase(BaseTestCase):
 | 
	
		
			
				|  |  |          """Create a minimal test case including all supported file types
 | 
	
		
			
				|  |  |          """
 | 
	
		
			
				|  |  |          # File
 | 
	
		
			
				|  |  | -        self.create_regular_file('empty', size=0)
 | 
	
		
			
				|  |  | -        os.utime('input/empty', (MAX_S, MAX_S))
 | 
	
		
			
				|  |  |          self.create_regular_file('file1', size=1024 * 80)
 | 
	
		
			
				|  |  |          self.create_regular_file('flagfile', size=1024)
 | 
	
		
			
				|  |  |          # Directory
 | 
	
	
		
			
				|  | @@ -370,6 +368,8 @@ class ArchiverTestCaseBase(BaseTestCase):
 | 
	
		
			
				|  |  |              if e.errno not in (errno.EINVAL, errno.ENOSYS):
 | 
	
		
			
				|  |  |                  raise
 | 
	
		
			
				|  |  |              have_root = False
 | 
	
		
			
				|  |  | +        time.sleep(1)  # "empty" must have newer timestamp than other files
 | 
	
		
			
				|  |  | +        self.create_regular_file('empty', size=0)
 | 
	
		
			
				|  |  |          return have_root
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -1591,9 +1591,8 @@ class ArchiverTestCase(ArchiverTestCaseBase):
 | 
	
		
			
				|  |  |          """test that various file status show expected results
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          clearly incomplete: only tests for the weird "unchanged" status for now"""
 | 
	
		
			
				|  |  | -        now = time.time()
 | 
	
		
			
				|  |  |          self.create_regular_file('file1', size=1024 * 80)
 | 
	
		
			
				|  |  | -        os.utime('input/file1', (now - 5, now - 5))  # 5 seconds ago
 | 
	
		
			
				|  |  | +        time.sleep(1)  # file2 must have newer timestamps than file1
 | 
	
		
			
				|  |  |          self.create_regular_file('file2', size=1024 * 80)
 | 
	
		
			
				|  |  |          self.cmd('init', '--encryption=repokey', self.repository_location)
 | 
	
		
			
				|  |  |          output = self.cmd('create', '--list', self.repository_location + '::test', 'input')
 | 
	
	
		
			
				|  | @@ -1606,12 +1605,51 @@ class ArchiverTestCase(ArchiverTestCaseBase):
 | 
	
		
			
				|  |  |          # https://borgbackup.readthedocs.org/en/latest/faq.html#i-am-seeing-a-added-status-for-a-unchanged-file
 | 
	
		
			
				|  |  |          self.assert_in("A input/file2", output)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    def test_file_status_cs_cache_mode(self):
 | 
	
		
			
				|  |  | +        """test that a changed file with faked "previous" mtime still gets backed up in ctime,size cache_mode"""
 | 
	
		
			
				|  |  | +        self.create_regular_file('file1', contents=b'123')
 | 
	
		
			
				|  |  | +        time.sleep(1)  # file2 must have newer timestamps than file1
 | 
	
		
			
				|  |  | +        self.create_regular_file('file2', size=10)
 | 
	
		
			
				|  |  | +        self.cmd('init', '--encryption=repokey', self.repository_location)
 | 
	
		
			
				|  |  | +        output = self.cmd('create', '--list', '--files-cache=ctime,size', self.repository_location + '::test1', 'input')
 | 
	
		
			
				|  |  | +        # modify file1, but cheat with the mtime (and atime) and also keep same size:
 | 
	
		
			
				|  |  | +        st = os.stat('input/file1')
 | 
	
		
			
				|  |  | +        self.create_regular_file('file1', contents=b'321')
 | 
	
		
			
				|  |  | +        os.utime('input/file1', ns=(st.st_atime_ns, st.st_mtime_ns))
 | 
	
		
			
				|  |  | +        # this mode uses ctime for change detection, so it should find file1 as modified
 | 
	
		
			
				|  |  | +        output = self.cmd('create', '--list', '--files-cache=ctime,size', self.repository_location + '::test2', 'input')
 | 
	
		
			
				|  |  | +        self.assert_in("A input/file1", output)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def test_file_status_ms_cache_mode(self):
 | 
	
		
			
				|  |  | +        """test that a chmod'ed file with no content changes does not get chunked again in mtime,size cache_mode"""
 | 
	
		
			
				|  |  | +        self.create_regular_file('file1', size=10)
 | 
	
		
			
				|  |  | +        time.sleep(1)  # file2 must have newer timestamps than file1
 | 
	
		
			
				|  |  | +        self.create_regular_file('file2', size=10)
 | 
	
		
			
				|  |  | +        self.cmd('init', '--encryption=repokey', self.repository_location)
 | 
	
		
			
				|  |  | +        output = self.cmd('create', '--list', '--files-cache=mtime,size', self.repository_location + '::test1', 'input')
 | 
	
		
			
				|  |  | +        # change mode of file1, no content change:
 | 
	
		
			
				|  |  | +        st = os.stat('input/file1')
 | 
	
		
			
				|  |  | +        os.chmod('input/file1', st.st_mode ^ stat.S_IRWXO)  # this triggers a ctime change, but mtime is unchanged
 | 
	
		
			
				|  |  | +        # this mode uses mtime for change detection, so it should find file1 as unmodified
 | 
	
		
			
				|  |  | +        output = self.cmd('create', '--list', '--files-cache=mtime,size', self.repository_location + '::test2', 'input')
 | 
	
		
			
				|  |  | +        self.assert_in("U input/file1", output)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def test_file_status_rc_cache_mode(self):
 | 
	
		
			
				|  |  | +        """test that files get rechunked unconditionally in rechunk,ctime cache mode"""
 | 
	
		
			
				|  |  | +        self.create_regular_file('file1', size=10)
 | 
	
		
			
				|  |  | +        time.sleep(1)  # file2 must have newer timestamps than file1
 | 
	
		
			
				|  |  | +        self.create_regular_file('file2', size=10)
 | 
	
		
			
				|  |  | +        self.cmd('init', '--encryption=repokey', self.repository_location)
 | 
	
		
			
				|  |  | +        output = self.cmd('create', '--list', '--files-cache=rechunk,ctime', self.repository_location + '::test1', 'input')
 | 
	
		
			
				|  |  | +        # no changes here, but this mode rechunks unconditionally
 | 
	
		
			
				|  |  | +        output = self.cmd('create', '--list', '--files-cache=rechunk,ctime', self.repository_location + '::test2', 'input')
 | 
	
		
			
				|  |  | +        self.assert_in("A input/file1", output)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      def test_file_status_excluded(self):
 | 
	
		
			
				|  |  |          """test that excluded paths are listed"""
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        now = time.time()
 | 
	
		
			
				|  |  |          self.create_regular_file('file1', size=1024 * 80)
 | 
	
		
			
				|  |  | -        os.utime('input/file1', (now - 5, now - 5))  # 5 seconds ago
 | 
	
		
			
				|  |  | +        time.sleep(1)  # file2 must have newer timestamps than file1
 | 
	
		
			
				|  |  |          self.create_regular_file('file2', size=1024 * 80)
 | 
	
		
			
				|  |  |          if has_lchflags:
 | 
	
		
			
				|  |  |              self.create_regular_file('file3', size=1024 * 80)
 | 
	
	
		
			
				|  | @@ -1647,9 +1685,8 @@ class ArchiverTestCase(ArchiverTestCaseBase):
 | 
	
		
			
				|  |  |          assert 'stats' in archive
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      def test_create_topical(self):
 | 
	
		
			
				|  |  | -        now = time.time()
 | 
	
		
			
				|  |  |          self.create_regular_file('file1', size=1024 * 80)
 | 
	
		
			
				|  |  | -        os.utime('input/file1', (now-5, now-5))
 | 
	
		
			
				|  |  | +        time.sleep(1)  # file2 must have newer timestamps than file1
 | 
	
		
			
				|  |  |          self.create_regular_file('file2', size=1024 * 80)
 | 
	
		
			
				|  |  |          self.cmd('init', '--encryption=repokey', self.repository_location)
 | 
	
		
			
				|  |  |          # no listing by default
 | 
	
	
		
			
				|  | @@ -2363,7 +2400,7 @@ class ArchiverTestCase(ArchiverTestCaseBase):
 | 
	
		
			
				|  |  |              fd.write(b'b' * 280)
 | 
	
		
			
				|  |  |          self.cmd('init', '--encryption=repokey', self.repository_location)
 | 
	
		
			
				|  |  |          self.cmd('create', '--chunker-params', '7,9,8,128', self.repository_location + '::test1', 'input')
 | 
	
		
			
				|  |  | -        self.cmd('create', self.repository_location + '::test2', 'input', '--no-files-cache')
 | 
	
		
			
				|  |  | +        self.cmd('create', self.repository_location + '::test2', 'input', '--files-cache=disabled')
 | 
	
		
			
				|  |  |          list = self.cmd('list', self.repository_location + '::test1', 'input/large_file',
 | 
	
		
			
				|  |  |                          '--format', '{num_chunks} {unique_chunks}')
 | 
	
		
			
				|  |  |          num_chunks, unique_chunks = map(int, list.split(' '))
 | 
	
	
		
			
				|  | @@ -3513,7 +3550,6 @@ class TestCommonOptions:
 | 
	
		
			
				|  |  |          add_common_option('-p', '--progress', dest='progress', action='store_true', help='foo')
 | 
	
		
			
				|  |  |          add_common_option('--lock-wait', dest='lock_wait', type=int, metavar='N', default=1,
 | 
	
		
			
				|  |  |                            help='(default: %(default)d).')
 | 
	
		
			
				|  |  | -        add_common_option('--no-files-cache', dest='no_files_cache', action='store_false', help='foo')
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      @pytest.fixture
 | 
	
		
			
				|  |  |      def basic_parser(self):
 | 
	
	
		
			
				|  | @@ -3555,7 +3591,6 @@ class TestCommonOptions:
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      def test_simple(self, parse_vars_from_line):
 | 
	
		
			
				|  |  |          assert parse_vars_from_line('--error') == {
 | 
	
		
			
				|  |  | -            'no_files_cache': True,
 | 
	
		
			
				|  |  |              'append': [],
 | 
	
		
			
				|  |  |              'lock_wait': 1,
 | 
	
		
			
				|  |  |              'log_level': 'error',
 | 
	
	
		
			
				|  | @@ -3563,7 +3598,6 @@ class TestCommonOptions:
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          assert parse_vars_from_line('--error', 'subcommand', '--critical') == {
 | 
	
		
			
				|  |  | -            'no_files_cache': True,
 | 
	
		
			
				|  |  |              'append': [],
 | 
	
		
			
				|  |  |              'lock_wait': 1,
 | 
	
		
			
				|  |  |              'log_level': 'critical',
 | 
	
	
		
			
				|  | @@ -3576,7 +3610,6 @@ class TestCommonOptions:
 | 
	
		
			
				|  |  |              parse_vars_from_line('--append-only', 'subcommand')
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          assert parse_vars_from_line('--append=foo', '--append', 'bar', 'subcommand', '--append', 'baz') == {
 | 
	
		
			
				|  |  | -            'no_files_cache': True,
 | 
	
		
			
				|  |  |              'append': ['foo', 'bar', 'baz'],
 | 
	
		
			
				|  |  |              'lock_wait': 1,
 | 
	
		
			
				|  |  |              'log_level': 'warning',
 | 
	
	
		
			
				|  | @@ -3589,7 +3622,6 @@ class TestCommonOptions:
 | 
	
		
			
				|  |  |      @pytest.mark.parametrize('flag,args_key,args_value', (
 | 
	
		
			
				|  |  |          ('-p', 'progress', True),
 | 
	
		
			
				|  |  |          ('--lock-wait=3', 'lock_wait', 3),
 | 
	
		
			
				|  |  | -        ('--no-files-cache', 'no_files_cache', False),
 | 
	
		
			
				|  |  |      ))
 | 
	
		
			
				|  |  |      def test_flag_position_independence(self, parse_vars_from_line, position, flag, args_key, args_value):
 | 
	
		
			
				|  |  |          line = []
 | 
	
	
		
			
				|  | @@ -3600,7 +3632,6 @@ class TestCommonOptions:
 | 
	
		
			
				|  |  |              line.append(flag)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          result = {
 | 
	
		
			
				|  |  | -            'no_files_cache': True,
 | 
	
		
			
				|  |  |              'append': [],
 | 
	
		
			
				|  |  |              'lock_wait': 1,
 | 
	
		
			
				|  |  |              'log_level': 'warning',
 |