1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
|
## @file
# Unit tests for workspace macro expansion.
#
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: BSD-2-Clause-Patent
#
"""Regression tests for workspace macro expansion."""
import os
import tempfile
import unittest
from unittest import mock
from Common.MultipleWorkspace import MultipleWorkspace
class TestMultipleWorkspace(unittest.TestCase):
"""Check workspace resolution without changing compiler option formatting."""
def setUp(self) -> None:
"""Create isolated roots and restore shared workspace state after each test."""
temporary = tempfile.TemporaryDirectory(prefix="multiple workspace ")
self.addCleanup(temporary.cleanup)
self.workspace = os.path.join(temporary.name, "workspace")
self.packages = [
os.path.join(temporary.name, "first package"),
os.path.join(temporary.name, "last package"),
]
for directory in [self.workspace] + self.packages:
os.mkdir(directory)
self.addCleanup(setattr, MultipleWorkspace, "WORKSPACE", MultipleWorkspace.WORKSPACE)
self.addCleanup(setattr, MultipleWorkspace, "PACKAGES_PATH", MultipleWorkspace.PACKAGES_PATH)
MultipleWorkspace.setWs(self.workspace, os.pathsep.join(self.packages))
def create_file(self, root: str, relative: str) -> str:
"""Create an empty file under a test root and return its path."""
path = os.path.join(root, relative)
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w"):
pass
return path
def test_workspace_takes_precedence(self) -> None:
"""Prefer an existing workspace file regardless of option quoting."""
relative = "Config/Settings.txt"
self.create_file(self.workspace, relative)
self.create_file(self.packages[0], relative)
for quote in ("", '"', "'"):
with self.subTest(quote=quote):
option = "--config=" + quote + "$(WORKSPACE)/" + relative + quote
self.assertEqual(
option.replace("$(WORKSPACE)", self.workspace),
MultipleWorkspace.handleWsMacro(option),
)
def test_quoted_paths_with_spaces(self) -> None:
"""Resolve quoted paths with spaces while retaining their original quoting."""
relative = "Config/Build Settings.txt"
self.create_file(self.packages[0], relative)
for option in (
'"$(WORKSPACE)/Config/Build Settings.txt"',
'--config="$(WORKSPACE)/Config/Build Settings.txt"',
"--config='$(WORKSPACE)/Config/Build Settings.txt'",
'"--config=$(WORKSPACE)/Config/Build Settings.txt"',
"'--config=$(WORKSPACE)/Config/Build Settings.txt'",
'--config="$(WORKSPACE)"/Config/"Build Settings.txt"',
):
with self.subTest(option=option):
self.assertEqual(
option.replace("$(WORKSPACE)", self.packages[0]),
MultipleWorkspace.handleWsMacro(option),
)
def test_opposite_quote_inside_quoted_path(self) -> None:
"""Preserve a literal apostrophe inside a double-quoted filename."""
relative = "Config/Developer's Settings.txt"
self.create_file(self.packages[0], relative)
option = '--config="$(WORKSPACE)/' + relative + '"'
self.assertEqual(
option.replace("$(WORKSPACE)", self.packages[0]),
MultipleWorkspace.handleWsMacro(option),
)
def test_first_existing_package_path_wins(self) -> None:
"""Prefer the first package root containing an unquoted path."""
relative = "Include/Header.h"
for directory in self.packages:
self.create_file(directory, relative)
option = "-I$(WORKSPACE)/" + relative
self.assertEqual(
option.replace("$(WORKSPACE)", self.packages[0]),
MultipleWorkspace.handleWsMacro(option),
)
def test_later_package_path_is_searched(self) -> None:
"""Search later package roots when earlier roots lack the file."""
relative = "Include/Header.h"
self.create_file(self.packages[1], relative)
option = '-I"$(WORKSPACE)/' + relative + '"'
self.assertEqual(
option.replace("$(WORKSPACE)", self.packages[1]),
MultipleWorkspace.handleWsMacro(option),
)
def test_missing_path_stays_workspace_relative(self) -> None:
"""Keep missing paths rooted in the workspace rather than a package root."""
for option in ("-I$(WORKSPACE)/Missing", '-I"$(WORKSPACE)/Missing Directory"'):
with self.subTest(option=option):
self.assertEqual(
option.replace("$(WORKSPACE)", self.workspace),
MultipleWorkspace.handleWsMacro(option),
)
def test_no_package_paths(self) -> None:
"""Expand the workspace macro when no package roots are configured."""
MultipleWorkspace.setWs(self.workspace)
option = '"$(WORKSPACE)/Missing Directory"'
self.assertEqual(
option.replace("$(WORKSPACE)", self.workspace),
MultipleWorkspace.handleWsMacro(option),
)
def test_no_macro_is_unchanged(self) -> None:
"""Leave strings without a workspace macro unchanged."""
for option in ("", ' -O2\t-DNAME="a b" ', "$(OTHER)/Include"):
with self.subTest(option=option):
self.assertEqual(option, MultipleWorkspace.handleWsMacro(option))
def test_multiple_options_preserve_whitespace_and_quotes(self) -> None:
"""Resolve each option independently without changing surrounding whitespace."""
self.create_file(self.workspace, "Config/Settings.txt")
self.create_file(self.packages[0], "Include/Header.h")
option = ' -O2\t--config="$(WORKSPACE)/Config/Settings.txt" -I$(WORKSPACE)/Include/Header.h\n'
expected = (
' -O2\t--config="'
+ self.workspace
+ '/Config/Settings.txt" -I'
+ self.packages[0]
+ "/Include/Header.h\n"
)
self.assertEqual(expected, MultipleWorkspace.handleWsMacro(option))
def test_windows_backslashes_are_preserved(self) -> None:
"""Check Windows-style paths without treating backslashes as escapes."""
MultipleWorkspace.WORKSPACE = r"C:\Main Workspace"
MultipleWorkspace.PACKAGES_PATH = [r"D:\First Package", r"E:\Last Package"]
option = r'/I"$(WORKSPACE)\Include Files\Header.h"'
path = r"D:\First Package\Include Files\Header.h"
with mock.patch("Common.LongFilePathOs.path.exists", side_effect=lambda candidate: candidate == path) as exists:
self.assertEqual(
option.replace("$(WORKSPACE)", MultipleWorkspace.PACKAGES_PATH[0]),
MultipleWorkspace.handleWsMacro(option),
)
self.assertEqual(
[
mock.call(r"C:\Main Workspace\Include Files\Header.h"),
mock.call(path),
],
exists.call_args_list,
)
def TheTestSuite() -> unittest.TestSuite:
"""Return the regression suite for the BaseTools test runner."""
return unittest.defaultTestLoader.loadTestsFromTestCase(TestMultipleWorkspace)
if __name__ == "__main__":
unittest.main()
|