1#!/usr/local/bin/python3.8
2
3# Copyright 2014-2015 Steven Watanabe
4# Distributed under the Boost Software License, Version 1.0.
5# (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
6
7# Tests the link-directory rule used to create the
8# common boost/ directory in the new git layout.
9
10import BoostBuild
11
12def ignore_config(t):
13    """These files are created by the configuration logic in link.jam
14    They may or may not exist, depending on the system."""
15    t.ignore("bin/symlink/test-hardlink")
16    t.ignore("bin/test-hardlink-source")
17    t.ignore("bin/test-symlink")
18    t.ignore("bin/test-symlink-source")
19
20def test_basic():
21    """Test creation of a single link"""
22    t = BoostBuild.Tester()
23    t.write("jamroot.jam", """\
24    import link ;
25    link-directory dir1-link : src/dir1/include : <location>. ;
26    """)
27
28    t.write("src/dir1/include/file1.h", "file1")
29
30    t.run_build_system()
31
32    t.expect_addition("include/file1.h")
33    t.expect_content("include/file1.h", "file1")
34    ignore_config(t)
35    t.expect_nothing_more()
36    t.cleanup()
37
38def test_merge_two():
39    """Test merging two directories"""
40    t = BoostBuild.Tester()
41    t.write("jamroot.jam", """\
42    import link ;
43    link-directory dir1-link : src/dir1/include : <location>. ;
44    link-directory dir2-link : src/dir2/include : <location>. ;
45    """)
46
47    t.write("src/dir1/include/file1.h", "file1")
48    t.write("src/dir2/include/file2.h", "file2")
49
50    t.run_build_system()
51
52    t.expect_addition("include/file1.h")
53    t.expect_content("include/file1.h", "file1")
54    t.expect_addition("include/file2.h")
55    t.expect_content("include/file2.h", "file2")
56    ignore_config(t)
57    t.expect_nothing_more()
58    t.cleanup()
59
60def test_merge_existing(group1, group2):
61    """Test adding a link when a different symlink already exists"""
62    t = BoostBuild.Tester()
63    t.write("jamroot.jam", """\
64    import link ;
65    link-directory dir1-link : src/dir1/include : <location>. ;
66    link-directory dir2-link : src/dir2/include : <location>. ;
67    """)
68
69    t.write("src/dir1/include/file1.h", "file1")
70    t.write("src/dir2/include/file2.h", "file2")
71
72    t.run_build_system(group1)
73
74    if "dir1-link" in group1:
75        t.expect_addition("include/file1.h")
76        t.expect_content("include/file1.h", "file1")
77    if "dir2-link" in group1:
78        t.expect_addition("include/file2.h")
79        t.expect_content("include/file2.h", "file2")
80    ignore_config(t)
81    t.expect_nothing_more()
82
83    t.run_build_system(group2)
84
85    if "dir1-link" in group2:
86        if "dir1-link" not in group1:
87            t.expect_addition("include/file1.h")
88        else:
89            # When a directory is split the link needs to be recreated.
90            # On Windows, the test system checks the mod time of the
91            # link rather than the link target, so this may be seen as
92            # an update.
93            t.ignore_touch("include/file1.h")
94        t.expect_content("include/file1.h", "file1")
95    else:
96        t.ignore_removal("include/file1.h")
97
98    if "dir2-link" in group2:
99        if "dir2-link" not in group1:
100            t.expect_addition("include/file2.h")
101        else:
102            t.ignore_touch("include/file2.h")
103        t.expect_content("include/file2.h", "file2")
104    else:
105        t.ignore_removal("include/file2.h")
106    ignore_config(t)
107    t.expect_nothing_more()
108
109    t.cleanup()
110
111def test_merge_existing_all():
112    test_merge_existing(["dir1-link"], ["dir2-link"])
113    test_merge_existing(["dir2-link"], ["dir1-link"])
114    test_merge_existing(["dir1-link"], ["dir1-link", "dir2-link"])
115    test_merge_existing(["dir2-link"], ["dir1-link", "dir2-link"])
116
117def test_merge_recursive():
118    "Test merging several directories including common prefixes"
119    t = BoostBuild.Tester()
120    t.write("jamroot.jam", """\
121    import link ;
122    link-directory dir1-link : src/dir1/include : <location>. ;
123    link-directory dir2-link : src/dir2/include : <location>. ;
124    link-directory dir3-link : src/dir3/include : <location>. ;
125    """)
126
127    t.write("src/dir1/include/file1.h", "file1")
128    t.write("src/dir2/include/file2.h", "file2")
129    t.write("src/dir2/include/nested/file3.h", "file3")
130    t.write("src/dir3/include/nested/file4.h", "file4")
131
132    t.run_build_system()
133
134    t.expect_addition("include/file1.h")
135    t.expect_content("include/file1.h", "file1")
136    t.expect_addition("include/file2.h")
137    t.expect_content("include/file2.h", "file2")
138    t.expect_addition("include/nested/file3.h")
139    t.expect_content("include/nested/file3.h", "file3")
140    t.expect_addition("include/nested/file4.h")
141    t.expect_content("include/nested/file4.h", "file4")
142    ignore_config(t)
143    t.expect_nothing_more()
144
145    t.cleanup()
146
147def test_merge_recursive_existing(group1, group2):
148    "Test merging several directories including common prefixes."
149    t = BoostBuild.Tester()
150    t.write("jamroot.jam", """\
151    import link ;
152    link-directory dir1-link : src/dir1/include : <location>. ;
153    link-directory dir2-link : src/dir2/include : <location>. ;
154    link-directory dir3-link : src/dir3/include : <location>. ;
155    link-directory dir4-link : src/dir4/include : <location>. ;
156    link-directory dir5-link : src/dir5/include : <location>. ;
157    """)
158
159    t.write("src/dir1/include/file1.h", "file1")
160    t.write("src/dir2/include/nested/file2.h", "file2")
161    t.write("src/dir3/include/nested/file3.h", "file3")
162    t.write("src/dir4/include/nested/xxx/yyy/file4.h", "file4")
163    t.write("src/dir5/include/nested/xxx/yyy/file5.h", "file5")
164
165    t.run_build_system(group1)
166    t.run_build_system(group2 + ["-d+12"])
167
168    def check_file(target, file):
169        if target in group2:
170            if target in group1:
171                t.ignore_touch(file)
172            else:
173                t.expect_addition(file)
174
175    check_file("dir1-link", "include/file1.h")
176    check_file("dir2-link", "include/nested/file2.h")
177    check_file("dir3-link", "include/nested/file3.h")
178    check_file("dir4-link", "include/nested/xxx/yyy/file4.h")
179    check_file("dir5-link", "include/nested/xxx/yyy/file5.h")
180    ignore_config(t)
181    t.expect_nothing_more()
182
183    t.cleanup()
184
185def test_merge_recursive_existing_all():
186    # These should create a link
187    test_merge_recursive_existing(["dir2-link"], ["dir2-link", "dir1-link"])
188    test_merge_recursive_existing(["dir2-link"], ["dir1-link", "dir2-link"])
189    # These should create a directory
190    test_merge_recursive_existing(["dir2-link"], ["dir2-link", "dir3-link"])
191    test_merge_recursive_existing(["dir2-link"], ["dir3-link", "dir2-link"])
192    # It should work even if we have to create many intermediate subdirectories
193    test_merge_recursive_existing(["dir4-link"], ["dir4-link", "dir5-link"])
194    test_merge_recursive_existing(["dir4-link"], ["dir5-link", "dir4-link"])
195
196def test_include_scan():
197    """Make sure that the #include scanner finds the headers"""
198    t = BoostBuild.Tester()
199    t.write("jamroot.jam", """\
200    import link ;
201    link-directory dir1-link : src/dir1/include : <location>. ;
202    link-directory dir2-link : src/dir2/include : <location>. ;
203    obj test : test.cpp :
204        <include>include
205        <implicit-dependency>dir1-link
206        <implicit-dependency>dir2-link ;
207    """)
208
209    t.write("src/dir1/include/file1.h", "#include <file2.h>\n")
210    t.write("src/dir2/include/file2.h", "int f();\n")
211    t.write("test.cpp", """\
212    #include <file1.h>
213    int main() { f(); }
214    """);
215
216    t.run_build_system(["test"])
217
218    t.expect_addition("bin/$toolset/debug*/test.obj")
219
220    t.run_build_system()
221    t.expect_nothing_more()
222
223    t.cleanup()
224
225def test_include_scan_merge_existing():
226    """Make sure that files are replaced if needed when merging in
227    a new directory"""
228    t = BoostBuild.Tester()
229
230    t.write("jamroot.jam", """\
231    import link ;
232    link-directory dir1-link : src/dir1/include : <location>. ;
233    link-directory dir2-link : src/dir2/include : <location>. ;
234    obj test : test.cpp :
235        <include>include
236        <implicit-dependency>dir1-link
237        <implicit-dependency>dir2-link ;
238    """)
239
240    t.write("src/dir1/include/file1.h", "int f();")
241    t.write("src/dir2/include/file2.h", "#include <file1.h>")
242    t.write("test.cpp", """\
243    #include <file2.h>
244    int main() { f(); }
245    """)
246
247    t.run_build_system(["dir2-link"])
248
249    t.run_build_system(["test"])
250    t.expect_addition("include/file1.h")
251    t.expect_addition("bin/$toolset/debug*/test.obj")
252    t.ignore_touch("include/file2.h")
253    t.expect_nothing_more()
254
255    t.cleanup()
256
257def test_update_file_link(params1, params2):
258    """Tests the behavior of updates when changing the link mode.
259    The link needs to be updated iff the original was a copy."""
260    t = BoostBuild.Tester()
261
262    t.write("jamroot.jam", """\
263    import link ;
264    import project ;
265    import property-set ;
266    import modules ;
267
268    if --no-symlinks in [ modules.peek : ARGV ]
269    {
270        modules.poke link : .can-symlink : false ;
271    }
272
273    if --no-hardlinks in [ modules.peek : ARGV ]
274    {
275        modules.poke link : .can-hardlink : false ;
276    }
277
278    .project = [ project.current ] ;
279    .has-files = [ glob include/file1.h ] ;
280
281    rule can-link ( properties * ) {
282        if ( ! [ link.can-symlink $(.project) ] ) &&
283           ( ! [ link.can-hardlink $(.project) ] )
284        {
285            ECHO links unsupported ;
286        }
287    }
288
289    # Use two directories so that we link to individual files.
290    link-directory dir1-link : src/dir1/include : <location>. ;
291    link-directory dir2-link : src/dir2/include : <location>. ;
292    alias check-linking : : <conditional>@can-link ;
293    """)
294    t.write("src/dir1/include/file1.h", "file1")
295    t.write("src/dir2/include/file2.h", "file2")
296
297    t.run_build_system(params1)
298    ignore_config(t)
299    t.expect_addition("include/file1.h")
300    t.expect_addition("include/file2.h")
301    t.expect_nothing_more()
302
303    using_links = "links unsupported" not in t.stdout()
304
305    t.touch("src/dir1/include/file1.h")
306
307    t.run_build_system(params2)
308    if not using_links: t.expect_touch("include/file1.h")
309    ignore_config(t)
310    t.expect_nothing_more()
311
312    t.cleanup()
313
314def test_update_file_link_all():
315    """Test all nine possible combinations of two runs."""
316    possible_args = [[], ["--no-symlinks"], ["--no-symlinks", "--no-hardlinks"]]
317    for arg1 in possible_args:
318        for arg2 in possible_args:
319            test_update_file_link(arg1, arg2)
320
321def test_error_duplicate():
322    """Test that linking a single file from
323    multiple sources causes a hard error."""
324    t = BoostBuild.Tester()
325
326    t.write("jamroot.jam", """\
327    import link ;
328    link-directory dir1-link : src/dir1/include : <location>. ;
329    link-directory dir2-link : src/dir2/include : <location>. ;
330    """)
331
332    t.write("src/dir1/include/file1.h", "file1")
333    t.write("src/dir2/include/file1.h", "file2")
334
335    t.run_build_system(status=1)
336    t.expect_output_lines(
337        ["error: Cannot create link include/file1.h to src/dir2/include/file1.h.",
338         "error: Link previously defined to another file, src/dir1/include/file1.h."])
339
340    t.cleanup()
341
342test_basic()
343test_merge_two()
344test_merge_existing_all()
345test_merge_recursive()
346test_merge_recursive_existing_all()
347test_include_scan()
348test_include_scan_merge_existing()
349test_update_file_link_all()
350test_error_duplicate()
351