1#!/usr/bin/env python
2#
3#  history_tests.py:  testing history-tracing code
4#
5#  Subversion is a tool for revision control.
6#  See http://subversion.apache.org for more information.
7#
8# ====================================================================
9#    Licensed to the Apache Software Foundation (ASF) under one
10#    or more contributor license agreements.  See the NOTICE file
11#    distributed with this work for additional information
12#    regarding copyright ownership.  The ASF licenses this file
13#    to you under the Apache License, Version 2.0 (the
14#    "License"); you may not use this file except in compliance
15#    with the License.  You may obtain a copy of the License at
16#
17#      http://www.apache.org/licenses/LICENSE-2.0
18#
19#    Unless required by applicable law or agreed to in writing,
20#    software distributed under the License is distributed on an
21#    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
22#    KIND, either express or implied.  See the License for the
23#    specific language governing permissions and limitations
24#    under the License.
25######################################################################
26
27# General modules
28import os
29
30# Our testing module
31import svntest
32from svntest import wc
33
34# (abbreviation)
35Skip = svntest.testcase.Skip_deco
36SkipUnless = svntest.testcase.SkipUnless_deco
37XFail = svntest.testcase.XFail_deco
38Issues = svntest.testcase.Issues_deco
39Issue = svntest.testcase.Issue_deco
40Wimp = svntest.testcase.Wimp_deco
41Item = wc.StateItem
42
43######################################################################
44# Tests
45#
46#   Each test must return on success or raise on failure.
47
48#----------------------------------------------------------------------
49
50def cat_traces_renames(sbox):
51  "verify that 'svn cat' traces renames"
52
53  sbox.build()
54  wc_dir = sbox.wc_dir
55  rho_path   = os.path.join(wc_dir, 'A', 'D', 'G', 'rho')
56  pi_path    = os.path.join(wc_dir, 'A', 'D', 'G', 'pi')
57  bloo_path  = os.path.join(wc_dir, 'A', 'D', 'G', 'bloo')
58
59  # rename rho to bloo. commit r2.
60  svntest.main.run_svn(None, 'mv', rho_path, bloo_path)
61
62  expected_output = svntest.wc.State(wc_dir, {
63    'A/D/G/rho' : Item(verb='Deleting'),
64    'A/D/G/bloo' : Item(verb='Adding')
65    })
66  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
67  expected_status.remove('A/D/G/rho')
68  expected_status.add({ 'A/D/G/bloo' :
69                        Item(wc_rev=2, status='  ') })
70
71  svntest.actions.run_and_verify_commit(wc_dir,
72                                        expected_output,
73                                        expected_status)
74
75  # rename pi to rho.  commit r3.
76  svntest.main.run_svn(None, 'mv', pi_path, rho_path)
77
78  # svn cat -r1 rho  --> should show pi's contents.
79  svntest.actions.run_and_verify_svn([ "This is the file 'pi'.\n"], [],
80                                     'cat',  '-r', '1', rho_path)
81
82  expected_output = svntest.wc.State(wc_dir, {
83    'A/D/G/pi' : Item(verb='Deleting'),
84    'A/D/G/rho' : Item(verb='Adding')
85    })
86  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
87  expected_status.remove('A/D/G/pi')
88  expected_status.tweak('A/D/G/rho', wc_rev=3)
89  expected_status.add({ 'A/D/G/bloo' :
90                        Item(wc_rev=2, status='  ') })
91
92  svntest.actions.run_and_verify_commit(wc_dir,
93                                        expected_output,
94                                        expected_status)
95
96  # update whole wc to HEAD
97  expected_output = svntest.wc.State(wc_dir, { }) # no output
98  expected_status.tweak(wc_rev=3)
99  expected_disk = svntest.main.greek_state.copy()
100  expected_disk.remove('A/D/G/pi', 'A/D/G/rho')
101  expected_disk.add({
102    'A/D/G/rho' : Item("This is the file 'pi'.\n"),
103    })
104  expected_disk.add({
105    'A/D/G/bloo' : Item("This is the file 'rho'.\n"),
106    })
107  svntest.actions.run_and_verify_update(wc_dir,
108                                        expected_output,
109                                        expected_disk,
110                                        expected_status)
111
112  # 'svn cat bloo' --> should show rho's contents.
113  svntest.actions.run_and_verify_svn([ "This is the file 'rho'.\n"], [],
114                                     'cat',  bloo_path)
115
116  # svn cat -r1 bloo --> should still show rho's contents.
117  svntest.actions.run_and_verify_svn([ "This is the file 'rho'.\n"], [],
118                                     'cat',  '-r', '1', bloo_path)
119
120  # svn cat -r1 rho  --> should show pi's contents.
121  svntest.actions.run_and_verify_svn([ "This is the file 'pi'.\n"], [],
122                                     'cat',  '-r', '1', rho_path)
123
124  # svn up -r1
125  svntest.actions.run_and_verify_svn(None, [], 'up', '-r', '1', wc_dir)
126  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
127  svntest.actions.run_and_verify_status(wc_dir, expected_status)
128
129  # svn cat -rHEAD rho --> should see 'unrelated object' error.
130  svntest.actions.run_and_verify_svn(None, svntest.verify.AnyOutput,
131                                     'cat',  '-r', 'HEAD', rho_path)
132
133@Issue(1970)
134def cat_avoids_false_identities(sbox):
135  "verify that 'svn cat' avoids false identities"
136
137  sbox.build()
138  wc_dir = sbox.wc_dir
139
140  # Issue #1970
141  #
142  # Highlight a bug in the client side use of the repository's
143  # location searching algorithm.
144  #
145  # The buggy history-following algorithm determines the paths that a
146  # line of history would be *expected to be* found in a given revision,
147  # but doesn't treat copies as gaps in the historical sequence.  If
148  # some other object fills those gaps at the same expected path, the
149  # client will find the wrong object.
150  #
151  # In the recipe below, iota gets created in r1.  In r2, it is
152  # deleted and replaced with an unrelated object at the same path.
153  # In r3, the interloper is deleted.  In r4, the original iota is
154  # resurrected via a copy from r1.
155  #
156  #     ,- - - - - - --.
157  #    o---| o---| o    o----->
158  #
159  #    |     |     |    |
160  #   r1    r2    r3   r4
161  #
162  # In a working copy at r4, running
163  #
164  #    $ svn cat -r2 iota
165  #
166  # should result in an error, but with the bug it instead cats the r2
167  # interloper.
168  #
169  # To reassure yourself that that's wrong, recall that the above
170  # command is equivalent to
171  #
172  #    $ svn cat -r2 iota@4
173  #
174  # Now do you see the evil that lies within us?
175
176  iota_path = os.path.join(wc_dir, 'iota')
177  iota_url = sbox.repo_url + '/iota'
178
179  # r2
180  svntest.main.run_svn(None, 'del', iota_path)
181  svntest.main.file_append(iota_path, "YOU SHOULD NOT SEE THIS\n")
182  svntest.main.run_svn(None, 'add', iota_path)
183  sbox.simple_commit(message='log msg')
184  svntest.main.run_svn(None, 'up', wc_dir)
185
186  # r3
187  svntest.main.run_svn(None, 'del', iota_path)
188  sbox.simple_commit(message='log msg')
189  svntest.main.run_svn(None, 'up', wc_dir)
190
191  # r4
192  svntest.main.run_svn(None, 'cp', iota_url + '@1', wc_dir)
193  sbox.simple_commit(message='log msg')
194  svntest.main.run_svn(None, 'up', wc_dir)
195
196  # 'svn cat -r2 iota' should error, because the line of history
197  # currently identified by /iota did not exist in r2, even though a
198  # totally unrelated file of the same name did.
199  svntest.actions.run_and_verify_svn(None, svntest.verify.AnyOutput,
200                                     'cat', '-r', '2', iota_path)
201
202
203########################################################################
204# Run the tests
205
206# list all tests here, starting with None:
207test_list = [ None,
208              cat_traces_renames,
209              cat_avoids_false_identities,
210              ]
211
212if __name__ == '__main__':
213  svntest.main.run_tests(test_list)
214  # NOTREACHED
215
216
217### End of file.
218