You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@buildstream.apache.org by gi...@apache.org on 2020/12/29 13:03:09 UTC

[buildstream] branch tristan/relative-search-paths created (now 84c67ce)

This is an automated email from the ASF dual-hosted git repository.

github-bot pushed a change to branch tristan/relative-search-paths
in repository https://gitbox.apache.org/repos/asf/buildstream.git.


      at 84c67ce  tests/plugins/search.py: Adding test to thoroughly exercise Element.search()

This branch includes the following new commits:

     new 926a918  element.py & loader: Support searching elements using full paths
     new 6165611  tests/integration/script.py: Test element relative search paths in Element.search()
     new 84c67ce  tests/plugins/search.py: Adding test to thoroughly exercise Element.search()

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[buildstream] 01/03: element.py & loader: Support searching elements using full paths

Posted by gi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

github-bot pushed a commit to branch tristan/relative-search-paths
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit 926a9186a226a5c2eb19d24f47eb2592c2edb484
Author: Tristan van Berkom <tr...@codethink.co.uk>
AuthorDate: Fri Jul 24 23:37:51 2020 +0900

    element.py & loader: Support searching elements using full paths
    
    This makes the following changes:
    
      * The LoadElements now keep a pointer to the resolved Elements, this
        allows quick resolution of Elements via the regular loader search
        resolution paths.
    
      * The Loader now removes the _clear_caches() code, the caches of
        loaded LoadElements are now persisted after the data model is loaded
        so that quick Element.search() can be performed.
    
      * The loader now exposes Loader.search_element() which returns a
        LoadElement
    
      * Element now implements Element.search() using the loader to resolve
        a project relative element path to a LoadElement, and returns the
        Element associated with the found LoadElement.
    
    This fixes #931.
---
 src/buildstream/_loader/loadelement.pyx | 15 +++++++++++++
 src/buildstream/_loader/loader.py       | 38 ++++++++++++++++-----------------
 src/buildstream/_loader/metaelement.py  |  3 +++
 src/buildstream/element.py              | 22 ++++++++++++-------
 4 files changed, 51 insertions(+), 27 deletions(-)

diff --git a/src/buildstream/_loader/loadelement.pyx b/src/buildstream/_loader/loadelement.pyx
index de2f96b..2bf1260 100644
--- a/src/buildstream/_loader/loadelement.pyx
+++ b/src/buildstream/_loader/loadelement.pyx
@@ -84,6 +84,7 @@ cdef class LoadElement:
     # TODO: if/when pyroaring exports symbols, we could type this statically
     cdef object _dep_cache
     cdef readonly list dependencies
+    cdef readonly object _element
 
     def __cinit__(self, MappingNode node, str filename, object loader):
 
@@ -103,6 +104,7 @@ cdef class LoadElement:
         #
         self._loader = loader   # The Loader object
         self._dep_cache = None  # The dependency cache, to speed up depends()
+        self._element = None    # The instantiated Element, if any
 
         #
         # Initialization
@@ -150,6 +152,19 @@ cdef class LoadElement:
     def junction(self):
         return self._loader.project.junction
 
+    # element()
+    #
+    # The Element property stores the instantiated Element, if an
+    # Element has been instantiated for this LoadElement
+    #
+    @property
+    def element(self):
+        return self._element
+
+    @element.setter
+    def element(self, value):
+        self._element = value
+
     # depends():
     #
     # Checks if this element depends on another element, directly
diff --git a/src/buildstream/_loader/loader.py b/src/buildstream/_loader/loader.py
index 9f88174..d2d14c3 100644
--- a/src/buildstream/_loader/loader.py
+++ b/src/buildstream/_loader/loader.py
@@ -170,8 +170,6 @@ class Loader:
             #
             ret.append(loader._collect_element(element))
 
-        self._clean_caches()
-
         # Cache how many Elements have just been loaded
         if self.load_context.task:
             # Workaround for task potentially being None (because no State object)
@@ -224,6 +222,24 @@ class Loader:
 
         return loader
 
+    # search_element()
+    #
+    # Search the project for a given LoadElement
+    #
+    # Args:
+    #    path (str): The relative element path
+    #
+    # Returns:
+    #    (LoadElement): The LoadElement
+    #
+    # Raises:
+    #    (LoadError): In case an element was not found, or would have
+    #                 required loading a project that is not yet loaded.
+    #
+    def search_element(self, path):
+        _, filename, loader = self._parse_name(path, None, load_subprojects=False)
+        return loader._load_file(filename, None, load_subprojects=False)
+
     # ancestors()
     #
     # This will traverse all active loaders in the ancestry for which this
@@ -319,6 +335,7 @@ class Loader:
             node.get_mapping(Symbol.PUBLIC, default={}),
             node.get_mapping(Symbol.SANDBOX, default={}),
             element_kind in ("junction", "link"),
+            element,
         )
 
         # Cache it now, make sure it's already there before recursing
@@ -905,20 +922,3 @@ class Loader:
                 ),
                 warning_token=CoreWarnings.BAD_CHARACTERS_IN_NAME,
             )
-
-    # _clean_caches()
-    #
-    # Clean internal loader caches, recursively
-    #
-    # When loading the elements, the loaders use caches in order to not load the
-    # same element twice. These are kept after loading and prevent garbage
-    # collection. Cleaning them explicitely is required.
-    #
-    def _clean_caches(self):
-        for loader in self._loaders.values():
-            # value may be None with nested junctions without overrides
-            if loader is not None:
-                loader._clean_caches()
-
-        self._meta_elements = {}
-        self._elements = {}
diff --git a/src/buildstream/_loader/metaelement.py b/src/buildstream/_loader/metaelement.py
index 1c1f6fe..50d1bc5 100644
--- a/src/buildstream/_loader/metaelement.py
+++ b/src/buildstream/_loader/metaelement.py
@@ -39,6 +39,7 @@ class MetaElement:
     #    public: Public domain data dictionary
     #    sandbox: Configuration specific to the sandbox environment
     #    first_pass: The element is to be loaded with first pass configuration (junction)
+    #    load_element (LoadElement): A reference back to the load_element
     #
     def __init__(
         self,
@@ -54,6 +55,7 @@ class MetaElement:
         public=None,
         sandbox=None,
         first_pass=False,
+        load_element=None,
     ):
         self.project = project
         self.name = name
@@ -71,3 +73,4 @@ class MetaElement:
         self.strict_dependencies = []
         self.first_pass = first_pass
         self.is_junction = kind in ("junction", "link")
+        self.load_element = load_element
diff --git a/src/buildstream/element.py b/src/buildstream/element.py
index 547397e..78f80bc 100644
--- a/src/buildstream/element.py
+++ b/src/buildstream/element.py
@@ -499,21 +499,23 @@ class Element(Plugin):
 
             yield from visit(self, scope, visited)
 
-    def search(self, scope: Scope, name: str) -> Optional["Element"]:
-        """Search for a dependency by name
+    def search(self, scope: Scope, path: str) -> Optional["Element"]:
+        """Search for a dependency by it's element relative search path
+
+        The search path as a relative path to the element, including
+        any junction names leading up to the element which might
+        be in a subproject.
 
         Args:
            scope: The scope to search
-           name: The dependency to search for
+           path: An element relative search path
 
         Returns:
            The dependency element, or None if not found.
         """
-        for dep in self.dependencies(scope):
-            if dep.name == name:
-                return dep
-
-        return None
+        project = self._get_project()
+        load_element = project.loader.search_element(path)
+        return load_element.element
 
     def node_subst_vars(self, node: "ScalarNode") -> str:
         """Replace any variables in the string contained in the node and returns it.
@@ -908,6 +910,10 @@ class Element(Plugin):
         element = meta.project.create_element(meta, first_pass=meta.first_pass)
         cls.__instantiated_elements[meta] = element
 
+        # Store a reference on the originating LoadElement, allowing us
+        # to perform optimal Element.search().
+        meta.load_element.element = element
+
         # Instantiate sources and generate their keys
         for meta_source in meta.sources:
             meta_source.first_pass = meta.is_junction


[buildstream] 02/03: tests/integration/script.py: Test element relative search paths in Element.search()

Posted by gi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

github-bot pushed a commit to branch tristan/relative-search-paths
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit 6165611ba47bd6c5139617e6e23d3068231c3844
Author: Tristan van Berkom <tr...@codethink.co.uk>
AuthorDate: Sat Jun 6 19:47:07 2020 +0900

    tests/integration/script.py: Test element relative search paths in Element.search()
    
    This adds some subprojects to the integration test, and extends the script
    layout test such that it tests:
    
      * The original layout test
    
      * Addressing elements in subprojects
    
      * Addressing elements in subprojects when also loaded as a subproject
        (i.e. this test that searches are element relative, and not
        simply searches for full element paths).
---
 .../script/script-cross-junction-layout.bst         | 21 +++++++++++++++++++++
 .../project/elements/script/script-deep-target.bst  |  4 ++++
 tests/integration/project/elements/subproject.bst   |  9 +++++++++
 tests/integration/project/project.conf              |  1 +
 .../project/subproject/elements/base.bst            |  5 +++++
 .../subproject/elements/base/base-alpine.bst        | 17 +++++++++++++++++
 .../script/script-cross-junction-layout.bst         | 21 +++++++++++++++++++++
 .../project/subproject/elements/script/script.bst   |  9 +++++++++
 .../project/subproject/elements/subsubproject.bst   |  9 +++++++++
 .../project/{ => subproject}/project.conf           |  6 +-----
 .../subproject/subsubproject/elements/base.bst      |  5 +++++
 .../subsubproject/elements/base/base-alpine.bst     | 17 +++++++++++++++++
 .../subsubproject/elements/script/script.bst        |  9 +++++++++
 .../{ => subproject/subsubproject}/project.conf     |  6 +-----
 tests/integration/script.py                         | 20 +++++++++++++++-----
 15 files changed, 144 insertions(+), 15 deletions(-)

diff --git a/tests/integration/project/elements/script/script-cross-junction-layout.bst b/tests/integration/project/elements/script/script-cross-junction-layout.bst
new file mode 100644
index 0000000..ad67835
--- /dev/null
+++ b/tests/integration/project/elements/script/script-cross-junction-layout.bst
@@ -0,0 +1,21 @@
+kind: script
+description: Write to root using a script element
+
+variables:
+  install-root: /buildstream/nstall
+  build-root: /buildstream/uild
+
+build-depends:
+  - subproject.bst:base.bst
+  - subproject.bst:script/script.bst
+
+config:
+  layout:
+    - element: subproject.bst:base.bst
+      destination: /
+
+    - element: subproject.bst:script/script.bst
+      destination: /buildstream/uild
+
+  commands:
+    - "cp %{build-root}/subtest %{install-root}"
diff --git a/tests/integration/project/elements/script/script-deep-target.bst b/tests/integration/project/elements/script/script-deep-target.bst
new file mode 100644
index 0000000..14935dd
--- /dev/null
+++ b/tests/integration/project/elements/script/script-deep-target.bst
@@ -0,0 +1,4 @@
+kind: stack
+
+depends:
+- subproject.bst:script/script-cross-junction-layout.bst
diff --git a/tests/integration/project/elements/subproject.bst b/tests/integration/project/elements/subproject.bst
new file mode 100644
index 0000000..0ac454f
--- /dev/null
+++ b/tests/integration/project/elements/subproject.bst
@@ -0,0 +1,9 @@
+kind: junction
+
+sources:
+- kind: local
+  path: subproject
+
+config:
+  options:
+    arch: "%{build_arch}"
diff --git a/tests/integration/project/project.conf b/tests/integration/project/project.conf
index 2d3da46..1a48f9e 100644
--- a/tests/integration/project/project.conf
+++ b/tests/integration/project/project.conf
@@ -10,6 +10,7 @@ options:
     type: bool
     description: Whether to expect a linux platform
     default: True
+    variable: linux
   arch:
     type: arch
     description: Current architecture
diff --git a/tests/integration/project/subproject/elements/base.bst b/tests/integration/project/subproject/elements/base.bst
new file mode 100644
index 0000000..428afa7
--- /dev/null
+++ b/tests/integration/project/subproject/elements/base.bst
@@ -0,0 +1,5 @@
+# elements/base.bst
+
+kind: stack
+depends:
+  - base/base-alpine.bst
diff --git a/tests/integration/project/subproject/elements/base/base-alpine.bst b/tests/integration/project/subproject/elements/base/base-alpine.bst
new file mode 100644
index 0000000..c583309
--- /dev/null
+++ b/tests/integration/project/subproject/elements/base/base-alpine.bst
@@ -0,0 +1,17 @@
+kind: import
+
+description: |
+  Alpine Linux base for tests
+
+  Generated using the `tests/integration-tests/base/generate-base.sh` script.
+
+sources:
+  - kind: tar
+    base-dir: ''
+    (?):
+    - arch == "x86-64":
+        ref: 3eb559250ba82b64a68d86d0636a6b127aa5f6d25d3601a79f79214dc9703639
+        url: "alpine:integration-tests-base.v1.x86_64.tar.xz"
+    - arch == "aarch64":
+        ref: 431fb5362032ede6f172e70a3258354a8fd71fcbdeb1edebc0e20968c792329a
+        url: "alpine:integration-tests-base.v1.aarch64.tar.xz"
diff --git a/tests/integration/project/subproject/elements/script/script-cross-junction-layout.bst b/tests/integration/project/subproject/elements/script/script-cross-junction-layout.bst
new file mode 100644
index 0000000..58ccdec
--- /dev/null
+++ b/tests/integration/project/subproject/elements/script/script-cross-junction-layout.bst
@@ -0,0 +1,21 @@
+kind: script
+description: Write to root using a script element
+
+variables:
+  install-root: /buildstream/nstall
+  build-root: /buildstream/uild
+
+build-depends:
+  - subsubproject.bst:base.bst
+  - subsubproject.bst:script/script.bst
+
+config:
+  layout:
+    - element: subsubproject.bst:base.bst
+      destination: /
+
+    - element: subsubproject.bst:script/script.bst
+      destination: /buildstream/uild
+
+  commands:
+    - "cp %{build-root}/subsubtest %{install-root}"
diff --git a/tests/integration/project/subproject/elements/script/script.bst b/tests/integration/project/subproject/elements/script/script.bst
new file mode 100644
index 0000000..1bb24f6
--- /dev/null
+++ b/tests/integration/project/subproject/elements/script/script.bst
@@ -0,0 +1,9 @@
+kind: script
+description: Script test
+
+build-depends:
+- base.bst
+
+config:
+  commands:
+    - "echo 'Hi' > %{install-root}/subtest"
diff --git a/tests/integration/project/subproject/elements/subsubproject.bst b/tests/integration/project/subproject/elements/subsubproject.bst
new file mode 100644
index 0000000..c38d621
--- /dev/null
+++ b/tests/integration/project/subproject/elements/subsubproject.bst
@@ -0,0 +1,9 @@
+kind: junction
+
+sources:
+- kind: local
+  path: subsubproject
+
+config:
+  options:
+    arch: "%{build_arch}"
diff --git a/tests/integration/project/project.conf b/tests/integration/project/subproject/project.conf
similarity index 79%
copy from tests/integration/project/project.conf
copy to tests/integration/project/subproject/project.conf
index 2d3da46..597157f 100644
--- a/tests/integration/project/project.conf
+++ b/tests/integration/project/subproject/project.conf
@@ -1,15 +1,11 @@
 # Project config for frontend build test
-name: test
+name: subtest
 min-version: 2.0
 element-path: elements
 aliases:
   alpine: https://bst-integration-test-images.ams3.cdn.digitaloceanspaces.com/
   project_dir: file://{project_dir}
 options:
-  linux:
-    type: bool
-    description: Whether to expect a linux platform
-    default: True
   arch:
     type: arch
     description: Current architecture
diff --git a/tests/integration/project/subproject/subsubproject/elements/base.bst b/tests/integration/project/subproject/subsubproject/elements/base.bst
new file mode 100644
index 0000000..428afa7
--- /dev/null
+++ b/tests/integration/project/subproject/subsubproject/elements/base.bst
@@ -0,0 +1,5 @@
+# elements/base.bst
+
+kind: stack
+depends:
+  - base/base-alpine.bst
diff --git a/tests/integration/project/subproject/subsubproject/elements/base/base-alpine.bst b/tests/integration/project/subproject/subsubproject/elements/base/base-alpine.bst
new file mode 100644
index 0000000..c583309
--- /dev/null
+++ b/tests/integration/project/subproject/subsubproject/elements/base/base-alpine.bst
@@ -0,0 +1,17 @@
+kind: import
+
+description: |
+  Alpine Linux base for tests
+
+  Generated using the `tests/integration-tests/base/generate-base.sh` script.
+
+sources:
+  - kind: tar
+    base-dir: ''
+    (?):
+    - arch == "x86-64":
+        ref: 3eb559250ba82b64a68d86d0636a6b127aa5f6d25d3601a79f79214dc9703639
+        url: "alpine:integration-tests-base.v1.x86_64.tar.xz"
+    - arch == "aarch64":
+        ref: 431fb5362032ede6f172e70a3258354a8fd71fcbdeb1edebc0e20968c792329a
+        url: "alpine:integration-tests-base.v1.aarch64.tar.xz"
diff --git a/tests/integration/project/subproject/subsubproject/elements/script/script.bst b/tests/integration/project/subproject/subsubproject/elements/script/script.bst
new file mode 100644
index 0000000..8c45dd8
--- /dev/null
+++ b/tests/integration/project/subproject/subsubproject/elements/script/script.bst
@@ -0,0 +1,9 @@
+kind: script
+description: Script test
+
+build-depends:
+- base.bst
+
+config:
+  commands:
+    - "echo 'Hi' > %{install-root}/subsubtest"
diff --git a/tests/integration/project/project.conf b/tests/integration/project/subproject/subsubproject/project.conf
similarity index 79%
copy from tests/integration/project/project.conf
copy to tests/integration/project/subproject/subsubproject/project.conf
index 2d3da46..1108d6c 100644
--- a/tests/integration/project/project.conf
+++ b/tests/integration/project/subproject/subsubproject/project.conf
@@ -1,15 +1,11 @@
 # Project config for frontend build test
-name: test
+name: subsubtest
 min-version: 2.0
 element-path: elements
 aliases:
   alpine: https://bst-integration-test-images.ams3.cdn.digitaloceanspaces.com/
   project_dir: file://{project_dir}
 options:
-  linux:
-    type: bool
-    description: Whether to expect a linux platform
-    default: True
   arch:
     type: arch
     description: Current architecture
diff --git a/tests/integration/script.py b/tests/integration/script.py
index 35a3fdd..3d23f38 100644
--- a/tests/integration/script.py
+++ b/tests/integration/script.py
@@ -147,19 +147,29 @@ def test_script_cwd(cli, datafiles):
 
 
 @pytest.mark.datafiles(DATA_DIR)
+@pytest.mark.parametrize(
+    "target,expect_file",
+    [
+        # Basic test of the layout
+        ("script/script-layout.bst", "test"),
+        # Tests a script which addresses subproject elements with Element.search()
+        ("script/script-cross-junction-layout.bst", "subtest"),
+        # Tests element relative paths in Element.search()
+        ("script/script-deep-target.bst", "subsubtest"),
+    ],
+)
 @pytest.mark.skipif(not HAVE_SANDBOX, reason="Only available with a functioning sandbox")
-def test_script_layout(cli, datafiles):
+def test_script_layout(cli, datafiles, target, expect_file):
     project = str(datafiles)
     checkout = os.path.join(cli.directory, "checkout")
-    element_name = "script/script-layout.bst"
 
-    res = cli.run(project=project, args=["build", element_name])
+    res = cli.run(project=project, args=["build", target])
     assert res.exit_code == 0
 
-    cli.run(project=project, args=["artifact", "checkout", element_name, "--directory", checkout])
+    cli.run(project=project, args=["artifact", "checkout", target, "--directory", checkout])
     assert res.exit_code == 0
 
-    with open(os.path.join(checkout, "test")) as f:
+    with open(os.path.join(checkout, expect_file)) as f:
         text = f.read()
 
     assert text == "Hi\n"


[buildstream] 03/03: tests/plugins/search.py: Adding test to thoroughly exercise Element.search()

Posted by gi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

github-bot pushed a commit to branch tristan/relative-search-paths
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit 84c67ce7952e32d6885908d494b5df04712077de
Author: Tristan van Berkom <tr...@codethink.co.uk>
AuthorDate: Sat Jul 25 13:18:59 2020 +0900

    tests/plugins/search.py: Adding test to thoroughly exercise Element.search()
---
 tests/plugins/search.py                            | 79 ++++++++++++++++++++++
 tests/plugins/search/manual-link.bst               |  4 ++
 tests/plugins/search/manual.bst                    |  1 +
 tests/plugins/search/project.conf                  | 12 ++++
 tests/plugins/search/search-link-in-subproject.bst |  7 ++
 tests/plugins/search/search-link-to-subproject.bst |  7 ++
 tests/plugins/search/search-link.bst               |  7 ++
 tests/plugins/search/search-manual.bst             |  7 ++
 tests/plugins/search/search-subproject.bst         |  7 ++
 tests/plugins/search/subproject-target-link.bst    |  4 ++
 tests/plugins/search/subproject.bst                |  8 +++
 tests/plugins/search/subproject/plugins/search.py  | 23 +++++++
 tests/plugins/search/subproject/project.conf       |  9 +++
 .../search-link-in-overridden-subsubproject.bst    |  7 ++
 .../subproject/search-link-in-subsubproject.bst    |  7 ++
 .../search-link-to-overridden-subsubproject.bst    |  7 ++
 .../subproject/search-link-to-subsubproject.bst    |  7 ++
 tests/plugins/search/subproject/search-link.bst    |  7 ++
 .../subproject/search-overridden-subsubproject.bst |  7 ++
 .../search/subproject/search-subsubproject.bst     |  7 ++
 tests/plugins/search/subproject/search-target.bst  |  7 ++
 tests/plugins/search/subproject/sub.txt            |  1 +
 .../subproject/subsubproject-target-link.bst       |  4 ++
 tests/plugins/search/subproject/subsubproject.bst  |  4 ++
 .../search/subproject/subsubproject/project.conf   |  2 +
 .../search/subproject/subsubproject/subsub.txt     |  1 +
 .../subproject/subsubproject/target-link.bst       |  4 ++
 .../search/subproject/subsubproject/target.bst     |  4 ++
 .../subproject/subsubproject2-target-link.bst      |  4 ++
 tests/plugins/search/subproject/subsubproject2.bst |  4 ++
 tests/plugins/search/subproject/target-link.bst    |  4 ++
 tests/plugins/search/subproject/target.bst         |  4 ++
 tests/plugins/search/subsubproject2.bst            |  4 ++
 tests/plugins/search/subsubproject2/project.conf   |  2 +
 tests/plugins/search/subsubproject2/subsub.txt     |  1 +
 .../plugins/search/subsubproject2/target-link.bst  |  4 ++
 tests/plugins/search/subsubproject2/target.bst     |  4 ++
 37 files changed, 282 insertions(+)

diff --git a/tests/plugins/search.py b/tests/plugins/search.py
new file mode 100644
index 0000000..d10886c
--- /dev/null
+++ b/tests/plugins/search.py
@@ -0,0 +1,79 @@
+# Pylint doesn't play well with fixtures and dependency injection from pytest
+# pylint: disable=redefined-outer-name
+
+import os
+import pytest
+
+from buildstream.testing import cli  # pylint: disable=unused-import
+
+DATA_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "search")
+
+
+####################################################
+#                     Tests                        #
+####################################################
+@pytest.mark.datafiles(DATA_DIR)
+@pytest.mark.parametrize(
+    "target",
+    [
+        # Search for an element in the same project which the element also depends on directly
+        #
+        "search-manual.bst",
+        #
+        # Search using a link to the manual element, where the manual element
+        # is listed as a dependency (ensures that link resolution works with
+        # Element.search())
+        #
+        "search-link.bst",
+        #
+        # Search for an element in a subproject which is also directly depended on
+        #
+        "search-subproject.bst",
+        #
+        # Search for a local link which links to a subproject element
+        #
+        "search-link-to-subproject.bst",
+        #
+        # Search for a link to a subproject element within that same subproject
+        #
+        "search-link-in-subproject.bst",
+        #
+        # Search for an element where the search element is in a subproject
+        #
+        "subproject.bst:search-target.bst",
+        #
+        # Search for an element via a link where the search element is in a subproject
+        #
+        "subproject.bst:search-link.bst",
+        #
+        # Search for an element in a subsubproject, where the search element is in a subproject
+        #
+        "subproject.bst:search-subsubproject.bst",
+        #
+        # Search for a link which links to a subsubproject element, within a subproject
+        #
+        "subproject.bst:search-link-to-subsubproject.bst",
+        #
+        # Search for a link to a subsubproject element within that same subsubproject, all
+        # within a subproject.
+        #
+        "subproject.bst:search-link-in-subsubproject.bst",
+        #
+        # Search for an element in an overridden subproject
+        #
+        "subproject.bst:search-overridden-subsubproject.bst",
+        #
+        # Search for a link which links to an overridden subsubproject element
+        #
+        "subproject.bst:search-link-to-overridden-subsubproject.bst",
+        #
+        # Search for a link to a subsubproject element within an overridden subsubproject.
+        #
+        "subproject.bst:search-link-in-overridden-subsubproject.bst",
+    ],
+)
+def test_search(cli, datafiles, target):
+    project = str(datafiles)
+
+    result = cli.run(project=project, args=["show", target])
+    result.assert_success()
diff --git a/tests/plugins/search/manual-link.bst b/tests/plugins/search/manual-link.bst
new file mode 100644
index 0000000..b309602
--- /dev/null
+++ b/tests/plugins/search/manual-link.bst
@@ -0,0 +1,4 @@
+kind: link
+
+config:
+  target: manual.bst
diff --git a/tests/plugins/search/manual.bst b/tests/plugins/search/manual.bst
new file mode 100644
index 0000000..4d7f702
--- /dev/null
+++ b/tests/plugins/search/manual.bst
@@ -0,0 +1 @@
+kind: manual
diff --git a/tests/plugins/search/project.conf b/tests/plugins/search/project.conf
new file mode 100644
index 0000000..5d52d24
--- /dev/null
+++ b/tests/plugins/search/project.conf
@@ -0,0 +1,12 @@
+# This project.conf gets rewritten for each plugin loading test
+name: test
+
+# Required BuildStream version
+min-version: 2.0
+
+# Configure the search element plugin
+plugins:
+- origin: junction
+  junction: subproject.bst
+  elements:
+  - search
diff --git a/tests/plugins/search/search-link-in-subproject.bst b/tests/plugins/search/search-link-in-subproject.bst
new file mode 100644
index 0000000..d7cbeb6
--- /dev/null
+++ b/tests/plugins/search/search-link-in-subproject.bst
@@ -0,0 +1,7 @@
+kind: search
+
+depends:
+- subproject.bst:target.bst
+
+config:
+  search: subproject.bst:target-link.bst
diff --git a/tests/plugins/search/search-link-to-subproject.bst b/tests/plugins/search/search-link-to-subproject.bst
new file mode 100644
index 0000000..e104da5
--- /dev/null
+++ b/tests/plugins/search/search-link-to-subproject.bst
@@ -0,0 +1,7 @@
+kind: search
+
+depends:
+- subproject.bst:target.bst
+
+config:
+  search: subproject-target-link.bst
diff --git a/tests/plugins/search/search-link.bst b/tests/plugins/search/search-link.bst
new file mode 100644
index 0000000..281292e
--- /dev/null
+++ b/tests/plugins/search/search-link.bst
@@ -0,0 +1,7 @@
+kind: search
+
+depends:
+- manual.bst
+
+config:
+  search: manual-link.bst
diff --git a/tests/plugins/search/search-manual.bst b/tests/plugins/search/search-manual.bst
new file mode 100644
index 0000000..9254cda
--- /dev/null
+++ b/tests/plugins/search/search-manual.bst
@@ -0,0 +1,7 @@
+kind: search
+
+depends:
+- manual.bst
+
+config:
+  search: manual.bst
diff --git a/tests/plugins/search/search-subproject.bst b/tests/plugins/search/search-subproject.bst
new file mode 100644
index 0000000..a2faf4b
--- /dev/null
+++ b/tests/plugins/search/search-subproject.bst
@@ -0,0 +1,7 @@
+kind: search
+
+depends:
+- subproject.bst:target.bst
+
+config:
+  search: subproject.bst:target.bst
diff --git a/tests/plugins/search/subproject-target-link.bst b/tests/plugins/search/subproject-target-link.bst
new file mode 100644
index 0000000..3da195d
--- /dev/null
+++ b/tests/plugins/search/subproject-target-link.bst
@@ -0,0 +1,4 @@
+kind: link
+
+config:
+  target: subproject.bst:target.bst
diff --git a/tests/plugins/search/subproject.bst b/tests/plugins/search/subproject.bst
new file mode 100644
index 0000000..edb1e3e
--- /dev/null
+++ b/tests/plugins/search/subproject.bst
@@ -0,0 +1,8 @@
+kind: junction
+sources:
+- kind: local
+  path: subproject
+
+config:
+  overrides:
+    subsubproject2.bst: subsubproject2.bst
diff --git a/tests/plugins/search/subproject/plugins/search.py b/tests/plugins/search/subproject/plugins/search.py
new file mode 100644
index 0000000..2f93163
--- /dev/null
+++ b/tests/plugins/search/subproject/plugins/search.py
@@ -0,0 +1,23 @@
+from buildstream import Element, Scope
+
+
+class Search(Element):
+    BST_MIN_VERSION = "2.0"
+
+    def configure(self, node):
+        self.search_element_path = node.get_str("search")
+        self.search_element = None
+
+    def preflight(self):
+        self.search_element = self.search(Scope.ALL, self.search_element_path)
+
+        assert self.search_element is not None
+        assert isinstance(self.search_element, Element)
+
+    def get_unique_key(self):
+        return {}
+
+
+# Plugin entry point
+def setup():
+    return Search
diff --git a/tests/plugins/search/subproject/project.conf b/tests/plugins/search/subproject/project.conf
new file mode 100644
index 0000000..8822950
--- /dev/null
+++ b/tests/plugins/search/subproject/project.conf
@@ -0,0 +1,9 @@
+name: subtest
+min-version: 2.0
+
+# Configure the search element plugin
+plugins:
+- origin: local
+  path: plugins
+  elements:
+  - search
diff --git a/tests/plugins/search/subproject/search-link-in-overridden-subsubproject.bst b/tests/plugins/search/subproject/search-link-in-overridden-subsubproject.bst
new file mode 100644
index 0000000..46f3e8d
--- /dev/null
+++ b/tests/plugins/search/subproject/search-link-in-overridden-subsubproject.bst
@@ -0,0 +1,7 @@
+kind: search
+
+depends:
+- subsubproject2.bst:target.bst
+
+config:
+  search: subsubproject2.bst:target-link.bst
diff --git a/tests/plugins/search/subproject/search-link-in-subsubproject.bst b/tests/plugins/search/subproject/search-link-in-subsubproject.bst
new file mode 100644
index 0000000..063e69c
--- /dev/null
+++ b/tests/plugins/search/subproject/search-link-in-subsubproject.bst
@@ -0,0 +1,7 @@
+kind: search
+
+depends:
+- subsubproject.bst:target.bst
+
+config:
+  search: subsubproject.bst:target-link.bst
diff --git a/tests/plugins/search/subproject/search-link-to-overridden-subsubproject.bst b/tests/plugins/search/subproject/search-link-to-overridden-subsubproject.bst
new file mode 100644
index 0000000..c97c55f
--- /dev/null
+++ b/tests/plugins/search/subproject/search-link-to-overridden-subsubproject.bst
@@ -0,0 +1,7 @@
+kind: search
+
+depends:
+- subsubproject2.bst:target.bst
+
+config:
+  search: subsubproject2-target-link.bst
diff --git a/tests/plugins/search/subproject/search-link-to-subsubproject.bst b/tests/plugins/search/subproject/search-link-to-subsubproject.bst
new file mode 100644
index 0000000..dddb2e8
--- /dev/null
+++ b/tests/plugins/search/subproject/search-link-to-subsubproject.bst
@@ -0,0 +1,7 @@
+kind: search
+
+depends:
+- subsubproject.bst:target.bst
+
+config:
+  search: subsubproject-target-link.bst
diff --git a/tests/plugins/search/subproject/search-link.bst b/tests/plugins/search/subproject/search-link.bst
new file mode 100644
index 0000000..a2bfa95
--- /dev/null
+++ b/tests/plugins/search/subproject/search-link.bst
@@ -0,0 +1,7 @@
+kind: search
+
+depends:
+- target.bst
+
+config:
+  search: target-link.bst
diff --git a/tests/plugins/search/subproject/search-overridden-subsubproject.bst b/tests/plugins/search/subproject/search-overridden-subsubproject.bst
new file mode 100644
index 0000000..dc6869b
--- /dev/null
+++ b/tests/plugins/search/subproject/search-overridden-subsubproject.bst
@@ -0,0 +1,7 @@
+kind: search
+
+depends:
+- subsubproject2.bst:target.bst
+
+config:
+  search: subsubproject2.bst:target.bst
diff --git a/tests/plugins/search/subproject/search-subsubproject.bst b/tests/plugins/search/subproject/search-subsubproject.bst
new file mode 100644
index 0000000..4175db5
--- /dev/null
+++ b/tests/plugins/search/subproject/search-subsubproject.bst
@@ -0,0 +1,7 @@
+kind: search
+
+depends:
+- subsubproject.bst:target.bst
+
+config:
+  search: subsubproject.bst:target.bst
diff --git a/tests/plugins/search/subproject/search-target.bst b/tests/plugins/search/subproject/search-target.bst
new file mode 100644
index 0000000..da59437
--- /dev/null
+++ b/tests/plugins/search/subproject/search-target.bst
@@ -0,0 +1,7 @@
+kind: search
+
+depends:
+- target.bst
+
+config:
+  search: target.bst
diff --git a/tests/plugins/search/subproject/sub.txt b/tests/plugins/search/subproject/sub.txt
new file mode 100644
index 0000000..f73f309
--- /dev/null
+++ b/tests/plugins/search/subproject/sub.txt
@@ -0,0 +1 @@
+file
diff --git a/tests/plugins/search/subproject/subsubproject-target-link.bst b/tests/plugins/search/subproject/subsubproject-target-link.bst
new file mode 100644
index 0000000..056b524
--- /dev/null
+++ b/tests/plugins/search/subproject/subsubproject-target-link.bst
@@ -0,0 +1,4 @@
+kind: link
+
+config:
+  target: subsubproject.bst:target.bst
diff --git a/tests/plugins/search/subproject/subsubproject.bst b/tests/plugins/search/subproject/subsubproject.bst
new file mode 100644
index 0000000..f535ab0
--- /dev/null
+++ b/tests/plugins/search/subproject/subsubproject.bst
@@ -0,0 +1,4 @@
+kind: junction
+sources:
+- kind: local
+  path: subsubproject
diff --git a/tests/plugins/search/subproject/subsubproject/project.conf b/tests/plugins/search/subproject/subsubproject/project.conf
new file mode 100644
index 0000000..d11bcbb
--- /dev/null
+++ b/tests/plugins/search/subproject/subsubproject/project.conf
@@ -0,0 +1,2 @@
+name: subsubtest
+min-version: 2.0
diff --git a/tests/plugins/search/subproject/subsubproject/subsub.txt b/tests/plugins/search/subproject/subsubproject/subsub.txt
new file mode 100644
index 0000000..f73f309
--- /dev/null
+++ b/tests/plugins/search/subproject/subsubproject/subsub.txt
@@ -0,0 +1 @@
+file
diff --git a/tests/plugins/search/subproject/subsubproject/target-link.bst b/tests/plugins/search/subproject/subsubproject/target-link.bst
new file mode 100644
index 0000000..451ae09
--- /dev/null
+++ b/tests/plugins/search/subproject/subsubproject/target-link.bst
@@ -0,0 +1,4 @@
+kind: link
+
+config:
+  target: target.bst
diff --git a/tests/plugins/search/subproject/subsubproject/target.bst b/tests/plugins/search/subproject/subsubproject/target.bst
new file mode 100644
index 0000000..afafac6
--- /dev/null
+++ b/tests/plugins/search/subproject/subsubproject/target.bst
@@ -0,0 +1,4 @@
+kind: import
+sources:
+- kind: local
+  path: subsub.txt
diff --git a/tests/plugins/search/subproject/subsubproject2-target-link.bst b/tests/plugins/search/subproject/subsubproject2-target-link.bst
new file mode 100644
index 0000000..6c44fe6
--- /dev/null
+++ b/tests/plugins/search/subproject/subsubproject2-target-link.bst
@@ -0,0 +1,4 @@
+kind: link
+
+config:
+  target: subsubproject2.bst:target.bst
diff --git a/tests/plugins/search/subproject/subsubproject2.bst b/tests/plugins/search/subproject/subsubproject2.bst
new file mode 100644
index 0000000..f535ab0
--- /dev/null
+++ b/tests/plugins/search/subproject/subsubproject2.bst
@@ -0,0 +1,4 @@
+kind: junction
+sources:
+- kind: local
+  path: subsubproject
diff --git a/tests/plugins/search/subproject/target-link.bst b/tests/plugins/search/subproject/target-link.bst
new file mode 100644
index 0000000..451ae09
--- /dev/null
+++ b/tests/plugins/search/subproject/target-link.bst
@@ -0,0 +1,4 @@
+kind: link
+
+config:
+  target: target.bst
diff --git a/tests/plugins/search/subproject/target.bst b/tests/plugins/search/subproject/target.bst
new file mode 100644
index 0000000..e24d9bb
--- /dev/null
+++ b/tests/plugins/search/subproject/target.bst
@@ -0,0 +1,4 @@
+kind: import
+sources:
+- kind: local
+  path: sub.txt
diff --git a/tests/plugins/search/subsubproject2.bst b/tests/plugins/search/subsubproject2.bst
new file mode 100644
index 0000000..7dbf22a
--- /dev/null
+++ b/tests/plugins/search/subsubproject2.bst
@@ -0,0 +1,4 @@
+kind: junction
+sources:
+- kind: local
+  path: subsubproject2
diff --git a/tests/plugins/search/subsubproject2/project.conf b/tests/plugins/search/subsubproject2/project.conf
new file mode 100644
index 0000000..af0de99
--- /dev/null
+++ b/tests/plugins/search/subsubproject2/project.conf
@@ -0,0 +1,2 @@
+name: subsubtest2
+min-version: 2.0
diff --git a/tests/plugins/search/subsubproject2/subsub.txt b/tests/plugins/search/subsubproject2/subsub.txt
new file mode 100644
index 0000000..f73f309
--- /dev/null
+++ b/tests/plugins/search/subsubproject2/subsub.txt
@@ -0,0 +1 @@
+file
diff --git a/tests/plugins/search/subsubproject2/target-link.bst b/tests/plugins/search/subsubproject2/target-link.bst
new file mode 100644
index 0000000..451ae09
--- /dev/null
+++ b/tests/plugins/search/subsubproject2/target-link.bst
@@ -0,0 +1,4 @@
+kind: link
+
+config:
+  target: target.bst
diff --git a/tests/plugins/search/subsubproject2/target.bst b/tests/plugins/search/subsubproject2/target.bst
new file mode 100644
index 0000000..afafac6
--- /dev/null
+++ b/tests/plugins/search/subsubproject2/target.bst
@@ -0,0 +1,4 @@
+kind: import
+sources:
+- kind: local
+  path: subsub.txt