blob: b62ad4c0ad50090e5de3c2ac3e90732084fcc871 [file]
## @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()