You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@buildstream.apache.org by tv...@apache.org on 2021/01/16 07:39:56 UTC

[buildstream] branch tristan/optional-project updated (eef0dd6 -> f3366e6)

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

tvb pushed a change to branch tristan/optional-project
in repository https://gitbox.apache.org/repos/asf/buildstream.git.


 discard eef0dd6  tests/frontend/artifact_show.py: Test artifact show without a project
 discard ab1f6ce  tests/frontend/artifact_pull.py: Test artifact pull without a project
 discard b649b6e  tests/frontend/artifact_log.py: Test artifact log without a project
 discard 6f69887  tests/frontend/artifact_delete.py: Test artifact deletion without a project
 discard 42e553c  tests/frontend/artifact_checkout.py: Test artifact checkout without project
 discard 1836786  tests/frontend/artifact_list_contents.py: Test listing artifact content without a project
 discard b48e14c  tests/frontend/artifact_list_contents.py: Use parametrized tests
 discard 8f0b85a  Allow certain operations to work without loading a project
 discard fb4b831  tests/frontend/artifact_checkout.py: Test checking out remote artifacts
 discard ff47c8e  tests/frontend/artifact_pull.py: Test pulling artifacts with various deps options
 discard dfda72e  _stream.py: Added new _reset() function.
 discard 7a30ded  _stream.py: Pre-emptive pulling of artifact metadata in some cases
 discard 99cccc7  _artifactelement.py: Override _pull_done()
 discard e039922  element.py: Added internal API _mimic_artifact()
 discard 412163c  _assetcache.py: Allow explicit re-initialization of remotes.
     add 8cad3f2  ci: Simplify pipelines using Docker Compose
     add 58fc11c  ci: Enable parallelism for tests
     add a666b0f  Merge pull request #1441 from apache/chandan/compose
     add 31b5587  _assetcache.py: Allow explicit re-initialization of remotes.
     add f122827  element.py: Added internal API _mimic_artifact()
     add 718baec  _artifactelement.py: Override _pull_done()
     add a920bbc  _stream.py: Pre-emptive pulling of artifact metadata in some cases
     add 9905ec3  _stream.py: Added new _reset() function.
     add 1115fb1  tests/frontend/artifact_pull.py: Test pulling artifacts with various deps options
     add e8233ab  tests/frontend/artifact_checkout.py: Test checking out remote artifacts
     new f628ec4  Allow certain operations to work without loading a project
     new 542e7dc  tests/frontend/artifact_list_contents.py: Use parametrized tests
     new 2e533b3  tests/frontend/artifact_list_contents.py: Test listing artifact content without a project
     new ddffddb  tests/frontend/artifact_checkout.py: Test artifact checkout without project
     new 015a514  tests/frontend/artifact_delete.py: Test artifact deletion without a project
     new 867a049  tests/frontend/artifact_log.py: Test artifact log without a project
     new 7dfbd14  tests/frontend/artifact_pull.py: Test artifact pull without a project
     new f3366e6  tests/frontend/artifact_show.py: Test artifact show without a project

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (eef0dd6)
            \
             N -- N -- N   refs/heads/tristan/optional-project (f3366e6)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 8 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.


Summary of changes:
 .github/common.env                    |   3 +
 .github/compose/ci.docker-compose.yml |  67 +++++++++++++++
 .github/workflows/ci.yml              | 153 +++++++---------------------------
 .github/workflows/merge.yml           |  25 +++---
 .github/workflows/release.yml         |  26 +++---
 5 files changed, 123 insertions(+), 151 deletions(-)
 create mode 100644 .github/common.env
 create mode 100644 .github/compose/ci.docker-compose.yml


[buildstream] 07/08: tests/frontend/artifact_pull.py: Test artifact pull without a project

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

tvb pushed a commit to branch tristan/optional-project
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit 7dfbd149860de8ce00063c943ebfa276940053a3
Author: Tristan van Berkom <tr...@codethink.co.uk>
AuthorDate: Thu Jan 14 16:21:14 2021 +0900

    tests/frontend/artifact_pull.py: Test artifact pull without a project
---
 tests/frontend/artifact_pull.py | 28 ++++++++++++++++++++++------
 1 file changed, 22 insertions(+), 6 deletions(-)

diff --git a/tests/frontend/artifact_pull.py b/tests/frontend/artifact_pull.py
index 6595106..4fa6b19 100644
--- a/tests/frontend/artifact_pull.py
+++ b/tests/frontend/artifact_pull.py
@@ -32,12 +32,17 @@ DATA_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "project")
     ],
     ids=["none", "build", "run", "all"],
 )
-def test_pull(cli, tmpdir, datafiles, deps, expect_cached):
+@pytest.mark.parametrize("with_project", [True, False], ids=["with-project", "without-project"])
+def test_pull(cli, tmpdir, datafiles, deps, expect_cached, with_project):
     project = str(datafiles)
 
     with create_artifact_share(os.path.join(str(tmpdir), "artifactshare")) as share:
-        # Build the element to push it to cache
-        cli.configure({"artifacts": {"url": share.repo, "push": True}})
+
+        # Build the element to push it to cache, and explicitly configure local cache so we can check it
+        local_cache = os.path.join(str(tmpdir), "cache")
+        cli.configure(
+            {"cachedir": local_cache, "artifacts": {"url": share.repo, "push": True},}
+        )
 
         # Build it
         result = cli.run(project=project, args=["build", "target.bst"])
@@ -50,11 +55,20 @@ def test_pull(cli, tmpdir, datafiles, deps, expect_cached):
         # Obtain the artifact name for pulling purposes
         artifact_name = cli.get_artifact_name(project, "test", "target.bst")
 
+        # Translate the expected element names into artifact names
+        expect_cached_artifacts = [
+            cli.get_artifact_name(project, "test", element_name) for element_name in expect_cached
+        ]
+
         # Discard the local cache
         shutil.rmtree(str(os.path.join(str(tmpdir), "cache", "cas")))
         shutil.rmtree(str(os.path.join(str(tmpdir), "cache", "artifacts")))
         assert cli.get_element_state(project, "target.bst") != "cached"
 
+        # Delete the project.conf if we're going to try this without a project
+        if not with_project:
+            os.remove(os.path.join(project, "project.conf"))
+
         # Now run our pull test
         result = cli.run(project=project, args=["artifact", "pull", "--deps", deps, artifact_name])
 
@@ -64,6 +78,8 @@ def test_pull(cli, tmpdir, datafiles, deps, expect_cached):
             result.assert_success()
 
         # After pulling, assert that we have the expected elements cached again.
-        states = cli.get_element_states(project, ["target.bst"])
-        for expect in expect_cached:
-            assert states[expect] == "cached"
+        #
+        # Note that we do not use cli.get_element_states() here because the project.conf
+        # might not be present, so we poke at the cache directly for this assertion.
+        for expect in expect_cached_artifacts:
+            assert os.path.exists(os.path.join(local_cache, "artifacts", "refs", expect))


[buildstream] 04/08: tests/frontend/artifact_checkout.py: Test artifact checkout without project

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

tvb pushed a commit to branch tristan/optional-project
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit ddffddb621bddbca4bb8da37515ebbc8ace4a2ef
Author: Tristan van Berkom <tr...@codethink.co.uk>
AuthorDate: Wed Jan 13 21:24:37 2021 +0900

    tests/frontend/artifact_checkout.py: Test artifact checkout without project
    
    Test that we can operate on artifact refs without the need to have a
    project.conf present locally.
---
 tests/frontend/artifact_checkout.py | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/tests/frontend/artifact_checkout.py b/tests/frontend/artifact_checkout.py
index 18c63a2..1375bc3 100644
--- a/tests/frontend/artifact_checkout.py
+++ b/tests/frontend/artifact_checkout.py
@@ -32,7 +32,8 @@ DATA_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "project")
     ],
     ids=["none", "build", "run", "all"],
 )
-def test_checkout(cli, tmpdir, datafiles, deps, expect_exist, expect_noexist):
+@pytest.mark.parametrize("with_project", [True, False], ids=["with-project", "without-project"])
+def test_checkout(cli, tmpdir, datafiles, deps, expect_exist, expect_noexist, with_project):
     project = str(datafiles)
     checkout = os.path.join(cli.directory, "checkout")
 
@@ -56,7 +57,11 @@ def test_checkout(cli, tmpdir, datafiles, deps, expect_exist, expect_noexist):
         shutil.rmtree(str(os.path.join(str(tmpdir), "cache", "artifacts")))
         assert cli.get_element_state(project, "target-import.bst") != "cached"
 
-        # Now checkout the artifacy
+        # Delete the project.conf if we're going to try this without a project
+        if not with_project:
+            os.remove(os.path.join(project, "project.conf"))
+
+        # Now checkout the artifact
         result = cli.run(
             project=project,
             args=["artifact", "checkout", "--directory", checkout, "--pull", "--deps", deps, artifact_name],


[buildstream] 05/08: tests/frontend/artifact_delete.py: Test artifact deletion without a project

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

tvb pushed a commit to branch tristan/optional-project
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit 015a514f0eb7af5d25e259d4c2be6b1faf02121a
Author: Tristan van Berkom <tr...@codethink.co.uk>
AuthorDate: Thu Jan 14 16:20:16 2021 +0900

    tests/frontend/artifact_delete.py: Test artifact deletion without a project
---
 tests/frontend/artifact_delete.py | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/tests/frontend/artifact_delete.py b/tests/frontend/artifact_delete.py
index 7b26a76..37b9731 100644
--- a/tests/frontend/artifact_delete.py
+++ b/tests/frontend/artifact_delete.py
@@ -50,7 +50,8 @@ def test_artifact_delete_element(cli, tmpdir, datafiles):
 
 # Test that we can delete an artifact by specifying its ref.
 @pytest.mark.datafiles(DATA_DIR)
-def test_artifact_delete_artifact(cli, tmpdir, datafiles):
+@pytest.mark.parametrize("with_project", [True, False], ids=["with-project", "without-project"])
+def test_artifact_delete_artifact(cli, tmpdir, datafiles, with_project):
     project = str(datafiles)
     element = "target.bst"
 
@@ -69,6 +70,10 @@ def test_artifact_delete_artifact(cli, tmpdir, datafiles):
     # Explicitly check that the ARTIFACT exists in the cache
     assert os.path.exists(os.path.join(local_cache, "artifacts", "refs", artifact))
 
+    # Delete the project.conf if we're going to try this without a project
+    if not with_project:
+        os.remove(os.path.join(project, "project.conf"))
+
     # Delete the artifact
     result = cli.run(project=project, args=["artifact", "delete", artifact])
     result.assert_success()


[buildstream] 03/08: tests/frontend/artifact_list_contents.py: Test listing artifact content without a project

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

tvb pushed a commit to branch tristan/optional-project
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit 2e533b3d8c58aa2100cb456ffaceb472416b097e
Author: Tristan van Berkom <tr...@codethink.co.uk>
AuthorDate: Thu Jan 14 16:20:34 2021 +0900

    tests/frontend/artifact_list_contents.py: Test listing artifact content without a project
---
 tests/frontend/artifact_list_contents.py | 22 ++++++++++++++++------
 1 file changed, 16 insertions(+), 6 deletions(-)

diff --git a/tests/frontend/artifact_list_contents.py b/tests/frontend/artifact_list_contents.py
index 6110d83..5003755 100644
--- a/tests/frontend/artifact_list_contents.py
+++ b/tests/frontend/artifact_list_contents.py
@@ -22,6 +22,7 @@ import os
 import pytest
 
 from buildstream.testing import cli  # pylint: disable=unused-import
+from buildstream.exceptions import ErrorDomain
 
 
 # Project directory
@@ -30,7 +31,8 @@ DATA_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "project",)
 
 @pytest.mark.datafiles(DATA_DIR)
 @pytest.mark.parametrize("target", ["element-name", "artifact-name"])
-def test_artifact_list_exact_contents(cli, datafiles, target):
+@pytest.mark.parametrize("with_project", [True, False], ids=["with-project", "without-project"])
+def test_artifact_list_exact_contents(cli, datafiles, target, with_project):
     project = str(datafiles)
 
     # Get the cache key of our test element
@@ -38,7 +40,7 @@ def test_artifact_list_exact_contents(cli, datafiles, target):
 
     # Ensure we have an artifact to read
     result = cli.run(project=project, args=["build", "import-bin.bst"])
-    assert result.exit_code == 0
+    result.assert_success()
 
     if target == "element-name":
         arg = "import-bin.bst"
@@ -46,14 +48,22 @@ def test_artifact_list_exact_contents(cli, datafiles, target):
         key = cli.get_element_key(project, "import-bin.bst")
         arg = "test/import-bin/" + key
 
+    # Delete the project.conf if we're going to try this without a project
+    if not with_project:
+        os.remove(os.path.join(project, "project.conf"))
+
     # List the contents via the key
     result = cli.run(project=project, args=["artifact", "list-contents", arg])
-    assert result.exit_code == 0
 
-    expected_output_template = "{target}:\n\tusr\n\tusr/bin\n\tusr/bin/hello\n\n"
-    expected_output = expected_output_template.format(target=arg)
+    # Expect to fail if we try to list by element name and there is no project
+    if target == "element-name" and not with_project:
+        result.assert_main_error(ErrorDomain.STREAM, "project-not-loaded")
+    else:
+        result.assert_success()
 
-    assert expected_output in result.output
+        expected_output_template = "{target}:\n\tusr\n\tusr/bin\n\tusr/bin/hello\n\n"
+        expected_output = expected_output_template.format(target=arg)
+        assert expected_output in result.output
 
 
 @pytest.mark.datafiles(DATA_DIR)


[buildstream] 02/08: tests/frontend/artifact_list_contents.py: Use parametrized tests

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

tvb pushed a commit to branch tristan/optional-project
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit 542e7dcf228d35496af6e3dda838ccb7fde13039
Author: Tristan van Berkom <tr...@codethink.co.uk>
AuthorDate: Thu Jan 14 15:46:11 2021 +0900

    tests/frontend/artifact_list_contents.py: Use parametrized tests
    
    Small refactor to reduce code duplication here
---
 tests/frontend/artifact_list_contents.py | 91 ++++++++++++--------------------
 1 file changed, 34 insertions(+), 57 deletions(-)

diff --git a/tests/frontend/artifact_list_contents.py b/tests/frontend/artifact_list_contents.py
index ee129cc..6110d83 100644
--- a/tests/frontend/artifact_list_contents.py
+++ b/tests/frontend/artifact_list_contents.py
@@ -29,36 +29,59 @@ DATA_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "project",)
 
 
 @pytest.mark.datafiles(DATA_DIR)
-def test_artifact_list_exact_contents_element(cli, datafiles):
+@pytest.mark.parametrize("target", ["element-name", "artifact-name"])
+def test_artifact_list_exact_contents(cli, datafiles, target):
     project = str(datafiles)
 
+    # Get the cache key of our test element
+    key = cli.get_element_key(project, "import-bin.bst")
+
     # Ensure we have an artifact to read
     result = cli.run(project=project, args=["build", "import-bin.bst"])
     assert result.exit_code == 0
 
-    # List the contents via the element name
-    result = cli.run(project=project, args=["artifact", "list-contents", "import-bin.bst"])
+    if target == "element-name":
+        arg = "import-bin.bst"
+    elif target == "artifact-name":
+        key = cli.get_element_key(project, "import-bin.bst")
+        arg = "test/import-bin/" + key
+
+    # List the contents via the key
+    result = cli.run(project=project, args=["artifact", "list-contents", arg])
     assert result.exit_code == 0
-    expected_output = "import-bin.bst:\n\tusr\n\tusr/bin\n\tusr/bin/hello\n\n"
+
+    expected_output_template = "{target}:\n\tusr\n\tusr/bin\n\tusr/bin/hello\n\n"
+    expected_output = expected_output_template.format(target=arg)
+
     assert expected_output in result.output
 
 
 @pytest.mark.datafiles(DATA_DIR)
-def test_artifact_list_exact_contents_ref(cli, datafiles):
+@pytest.mark.parametrize("target", ["element-name", "artifact-name"])
+def test_artifact_list_exact_contents_long(cli, datafiles, target):
     project = str(datafiles)
 
-    # Get the cache key of our test element
-    key = cli.get_element_key(project, "import-bin.bst")
-
     # Ensure we have an artifact to read
     result = cli.run(project=project, args=["build", "import-bin.bst"])
     assert result.exit_code == 0
 
-    # List the contents via the key
-    result = cli.run(project=project, args=["artifact", "list-contents", "test/import-bin/" + key])
+    if target == "element-name":
+        arg = "import-bin.bst"
+    elif target == "artifact-name":
+        key = cli.get_element_key(project, "import-bin.bst")
+        arg = "test/import-bin/" + key
+
+    # List the contents via the element name
+    result = cli.run(project=project, args=["artifact", "list-contents", "--long", arg])
     assert result.exit_code == 0
+    expected_output_template = (
+        "{target}:\n"
+        "\tdrwxr-xr-x  dir    1           usr\n"
+        "\tdrwxr-xr-x  dir    1           usr/bin\n"
+        "\t-rw-r--r--  reg    107         usr/bin/hello\n\n"
+    )
+    expected_output = expected_output_template.format(target=arg)
 
-    expected_output = "test/import-bin/" + key + ":\n" "\tusr\n" "\tusr/bin\n" "\tusr/bin/hello\n\n"
     assert expected_output in result.output
 
 
@@ -89,49 +112,3 @@ def test_artifact_list_exact_contents_glob(cli, datafiles):
 
     for artifact in expected_artifacts:
         assert artifact in result.output
-
-
-@pytest.mark.datafiles(DATA_DIR)
-def test_artifact_list_exact_contents_element_long(cli, datafiles):
-    project = str(datafiles)
-
-    # Ensure we have an artifact to read
-    result = cli.run(project=project, args=["build", "import-bin.bst"])
-    assert result.exit_code == 0
-
-    # List the contents via the element name
-    result = cli.run(project=project, args=["artifact", "list-contents", "--long", "import-bin.bst"])
-    assert result.exit_code == 0
-    expected_output = (
-        "import-bin.bst:\n"
-        "\tdrwxr-xr-x  dir    1           usr\n"
-        "\tdrwxr-xr-x  dir    1           usr/bin\n"
-        "\t-rw-r--r--  reg    107         usr/bin/hello\n\n"
-    )
-
-    assert expected_output in result.output
-
-
-@pytest.mark.datafiles(DATA_DIR)
-def test_artifact_list_exact_contents_ref_long(cli, datafiles):
-    project = str(datafiles)
-
-    # Get the cache key of our test element
-    key = cli.get_element_key(project, "import-bin.bst")
-
-    # Ensure we have an artifact to read
-    result = cli.run(project=project, args=["build", "import-bin.bst"])
-    assert result.exit_code == 0
-
-    # List the contents via the key
-    result = cli.run(project=project, args=["artifact", "list-contents", "-l", "test/import-bin/" + key])
-    assert result.exit_code == 0
-
-    expected_output = (
-        "  test/import-bin/" + key + ":\n"
-        "\tdrwxr-xr-x  dir    1           usr\n"
-        "\tdrwxr-xr-x  dir    1           usr/bin\n"
-        "\t-rw-r--r--  reg    107         usr/bin/hello\n\n"
-    )
-
-    assert expected_output in result.output


[buildstream] 06/08: tests/frontend/artifact_log.py: Test artifact log without a project

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

tvb pushed a commit to branch tristan/optional-project
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit 867a0497a0e8d2e647701dcc5b9b2c82850662c3
Author: Tristan van Berkom <tr...@codethink.co.uk>
AuthorDate: Thu Jan 14 16:20:54 2021 +0900

    tests/frontend/artifact_log.py: Test artifact log without a project
---
 tests/frontend/artifact_log.py | 34 +++++++++++++++++++---------------
 1 file changed, 19 insertions(+), 15 deletions(-)

diff --git a/tests/frontend/artifact_log.py b/tests/frontend/artifact_log.py
index 8fd51ea..07efa56 100644
--- a/tests/frontend/artifact_log.py
+++ b/tests/frontend/artifact_log.py
@@ -30,7 +30,9 @@ DATA_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "project",)
 
 
 @pytest.mark.datafiles(DATA_DIR)
-def test_artifact_log(cli, datafiles):
+@pytest.mark.parametrize("target", ["artifact", "artifact-glob"])
+@pytest.mark.parametrize("with_project", [True, False], ids=["with-project", "without-project"])
+def test_artifact_log(cli, datafiles, target, with_project):
     project = str(datafiles)
 
     # Get the cache key of our test element
@@ -43,26 +45,28 @@ def test_artifact_log(cli, datafiles):
 
     # Ensure we have an artifact to read
     result = cli.run(project=project, args=["build", "target.bst"])
-    assert result.exit_code == 0
+    result.assert_success()
 
-    # Read the log via the element name
+    # Collect the log by running `bst artifact log` on the element name first
     result = cli.run(project=project, args=["artifact", "log", "target.bst"])
-    assert result.exit_code == 0
+    result.assert_success()
     log = result.output
-
-    # Assert that there actually was a log file
     assert log != ""
 
-    # Read the log via the key
-    result = cli.run(project=project, args=["artifact", "log", "test/target/" + key])
-    assert result.exit_code == 0
-    assert log == result.output
+    # Delete the project.conf if we're going to try this without a project
+    if not with_project:
+        os.remove(os.path.join(project, "project.conf"))
 
-    # Read the log via glob
-    result = cli.run(project=project, args=["artifact", "log", "test/target/*"])
-    assert result.exit_code == 0
-    # The artifact is cached under both a strong key and a weak key
-    assert log == result.output
+    args = ["artifact", "log"]
+    if target == "artifact":
+        args.append("test/target/{}".format(key))
+    elif target == "artifact-glob":
+        args.append("test/target/*")
+
+    # Run bst artifact log
+    result = cli.run(project=project, args=args)
+    result.assert_success()
+    assert result.output == log
 
 
 @pytest.mark.datafiles(DATA_DIR)


[buildstream] 01/08: Allow certain operations to work without loading a project

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

tvb pushed a commit to branch tristan/optional-project
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit f628ec42bdd4bac3476e1b479910c626ee97356f
Author: Tristan van Berkom <tr...@codethink.co.uk>
AuthorDate: Thu Nov 19 16:35:33 2020 +0900

    Allow certain operations to work without loading a project
    
    This patch removes the requirement to load a project.conf at startup time,
    accomodating certain operations which do not need to observe workspace
    state or load elements.
    
    Summary of changes:
    
      * _frontend/app.py: Don't error out if a project cannot be loaded.
    
        The project is optional, and Stream() will raise an error if a project
        is required for the operation it is asked to perform.
    
      * _frontend/widget.py: Support printing the heading without a toplevel
        project.
    
      * _stream.py: Handle cases where the main project is None, and
        raise an error in cases where a project is required.
    
      * _workspaces.py: Handle cases where the project directory is None,
        in case we are looking at an ArtifactProject which has no directory.
    
      * tests/format/project.py: Update the missing project test to expect
        a different error, and observe multiple cases where a project might
        be required.
    
        We still have an error for missing projects, but it is only issued
        for cases where a project is expected, we need to continue testing this.
---
 src/buildstream/_frontend/app.py    | 23 ++++++------
 src/buildstream/_frontend/widget.py | 12 +++++--
 src/buildstream/_stream.py          | 72 +++++++++++++++++++++++++++----------
 src/buildstream/_workspaces.py      | 10 ++++--
 tests/format/project.py             |  7 ++--
 5 files changed, 86 insertions(+), 38 deletions(-)

diff --git a/src/buildstream/_frontend/app.py b/src/buildstream/_frontend/app.py
index 9937470..ce1610f 100644
--- a/src/buildstream/_frontend/app.py
+++ b/src/buildstream/_frontend/app.py
@@ -284,23 +284,24 @@ class App:
                     cli_options=self._main_options["option"],
                     default_mirror=self._main_options.get("default_mirror"),
                 )
-
-                self.stream.set_project(self.project)
             except LoadError as e:
 
-                # Help users that are new to BuildStream by suggesting 'init'.
-                # We don't want to slow down users that just made a mistake, so
-                # don't stop them with an offer to create a project for them.
-                if e.reason == LoadErrorReason.MISSING_PROJECT_CONF:
-                    click.echo("No project found. You can create a new project like so:", err=True)
-                    click.echo("", err=True)
-                    click.echo("    bst init", err=True)
-
-                self._error_exit(e, "Error loading project")
+                # If there was no project.conf at all then there was just no project found.
+                #
+                # Don't error out in this case, as Stream() supports some operations which
+                # do not require a project. If Stream() requires a project and it is missing,
+                # then it will raise an error.
+                #
+                if e.reason != LoadErrorReason.MISSING_PROJECT_CONF:
+                    self._error_exit(e, "Error loading project")
 
             except BstError as e:
                 self._error_exit(e, "Error loading project")
 
+            # Set the project on the Stream, this can be None if there is no project.
+            #
+            self.stream.set_project(self.project)
+
             # Run the body of the session here, once everything is loaded
             try:
                 yield
diff --git a/src/buildstream/_frontend/widget.py b/src/buildstream/_frontend/widget.py
index 0d5379f..dfc340a 100644
--- a/src/buildstream/_frontend/widget.py
+++ b/src/buildstream/_frontend/widget.py
@@ -444,7 +444,7 @@ class LogLine(Widget):
     # and so on.
     #
     # Args:
-    #    toplevel_project (Project): The toplevel project we were invoked from
+    #    toplevel_project (Project): The toplevel project we were invoked from, or None
     #    stream (Stream): The stream
     #    log_file (file): An optional file handle for additional logging
     #
@@ -460,7 +460,8 @@ class LogLine(Widget):
         text += self.content_profile.fmt("BuildStream Version {}\n".format(bst_version), bold=True)
         values = OrderedDict()
         values["Session Start"] = starttime.strftime("%A, %d-%m-%Y at %H:%M:%S")
-        values["Project"] = "{} ({})".format(toplevel_project.name, toplevel_project.directory)
+        if toplevel_project:
+            values["Project"] = "{} ({})".format(toplevel_project.name, toplevel_project.directory)
         values["Targets"] = ", ".join([t.name for t in stream.targets])
         text += self._format_values(values)
 
@@ -483,7 +484,12 @@ class LogLine(Widget):
 
         # Print information about each loaded project
         #
-        for project_info in toplevel_project.loaded_projects():
+        if toplevel_project:
+            loaded_projects = toplevel_project.loaded_projects()
+        else:
+            loaded_projects = []
+
+        for project_info in loaded_projects:
             project = project_info.project
 
             # Project title line
diff --git a/src/buildstream/_stream.py b/src/buildstream/_stream.py
index 22feab9..6eb25e8 100644
--- a/src/buildstream/_stream.py
+++ b/src/buildstream/_stream.py
@@ -124,7 +124,8 @@ class Stream:
     def set_project(self, project):
         assert self._project is None
         self._project = project
-        self._project.load_context.set_fetch_subprojects(self._fetch_subprojects)
+        if self._project:
+            self._project.load_context.set_fetch_subprojects(self._fetch_subprojects)
 
     # load_selection()
     #
@@ -885,6 +886,8 @@ class Stream:
     #    remove_dir (bool): Whether to remove the associated directory
     #
     def workspace_close(self, element_name, *, remove_dir):
+        self._assert_project("Unable to locate workspaces")
+
         workspaces = self._context.get_workspaces()
         workspace = workspaces.get_workspace(element_name)
 
@@ -913,6 +916,7 @@ class Stream:
     #    soft (bool): Only set the workspace state to not prepared
     #
     def workspace_reset(self, targets, *, soft):
+        self._assert_project("Unable to locate workspaces")
 
         elements = self._load(targets, selection=_PipelineSelection.REDIRECT)
 
@@ -953,6 +957,8 @@ class Stream:
     # True if there are any existing workspaces.
     #
     def workspace_exists(self, element_name=None):
+        self._assert_project("Unable to locate workspaces")
+
         workspaces = self._context.get_workspaces()
         if element_name:
             workspace = workspaces.get_workspace(element_name)
@@ -968,6 +974,8 @@ class Stream:
     # Serializes the workspaces and dumps them in YAML to stdout.
     #
     def workspace_list(self):
+        self._assert_project("Unable to locate workspaces")
+
         workspaces = []
         for element_name, workspace_ in self._context.get_workspaces().list():
             workspace_detail = {
@@ -1106,6 +1114,22 @@ class Stream:
     #                    Private Methods                        #
     #############################################################
 
+    # _assert_project()
+    #
+    # Raises an assertion of a project was not loaded
+    #
+    # Args:
+    #    message: The user facing error message, e.g. "Unable to load elements"
+    #
+    # Raises:
+    #    A StreamError with reason "project-not-loaded" is raised if no project was loaded
+    #
+    def _assert_project(self, message: str) -> None:
+        if not self._project:
+            raise StreamError(
+                message, detail="No project.conf or active workspace was located", reason="project-not-loaded"
+            )
+
     # _fetch_subprojects()
     #
     # Fetch subprojects as part of the project and element loading process.
@@ -1210,7 +1234,12 @@ class Stream:
             targets, valid_artifact_names=valid_artifact_names
         )
 
-        self._project.load_context.set_rewritable(rewritable)
+        # We need a project in order to load elements
+        if element_names:
+            self._assert_project("Unable to load elements: {}".format(", ".join(element_names)))
+
+        if self._project:
+            self._project.load_context.set_rewritable(rewritable)
 
         # Load elements and except elements
         if element_names:
@@ -1493,7 +1522,11 @@ class Stream:
     def _resolve_elements(self, targets):
         with self._context.messenger.simple_task("Resolving cached state", silent_nested=True) as task:
             # We need to go through the project to access the loader
-            if task:
+            #
+            # FIXME: We need to calculate the total elements to resolve differently so that
+            #        it can include artifact elements
+            #
+            if task and self._project:
                 task.set_maximum_progress(self._project.loader.loaded)
 
             # XXX: Now that Element._update_state() can trigger recursive update_state calls
@@ -1845,22 +1878,23 @@ class Stream:
             element_targets = initial_targets
 
         # Expand globs for elements
-        all_elements = []
-        element_path_length = len(self._project.element_path) + 1
-        for dirpath, _, filenames in os.walk(self._project.element_path):
-            for filename in filenames:
-                if filename.endswith(".bst"):
-                    element_path = os.path.join(dirpath, filename)
-                    element_path = element_path[element_path_length:]  # Strip out the element_path
-                    all_elements.append(element_path)
-
-        for glob in globs:
-            matched = False
-            for element_path in utils.glob(all_elements, glob):
-                element_targets.append(element_path)
-                matched = True
-            if matched:
-                globs[glob] = globs[glob] + 1
+        if self._project:
+            all_elements = []
+            element_path_length = len(self._project.element_path) + 1
+            for dirpath, _, filenames in os.walk(self._project.element_path):
+                for filename in filenames:
+                    if filename.endswith(".bst"):
+                        element_path = os.path.join(dirpath, filename)
+                        element_path = element_path[element_path_length:]  # Strip out the element_path
+                        all_elements.append(element_path)
+
+            for glob in globs:
+                matched = False
+                for element_path in utils.glob(all_elements, glob):
+                    element_targets.append(element_path)
+                    matched = True
+                if matched:
+                    globs[glob] = globs[glob] + 1
 
         # Expand globs for artifact names
         if valid_artifact_names:
diff --git a/src/buildstream/_workspaces.py b/src/buildstream/_workspaces.py
index e51be08..ebca148 100644
--- a/src/buildstream/_workspaces.py
+++ b/src/buildstream/_workspaces.py
@@ -313,10 +313,16 @@ class Workspace:
 class Workspaces:
     def __init__(self, toplevel_project, workspace_project_cache):
         self._toplevel_project = toplevel_project
-        self._bst_directory = os.path.join(toplevel_project.directory, ".bst")
-        self._workspaces = self._load_config()
         self._workspace_project_cache = workspace_project_cache
 
+        # A project without a directory can happen
+        if toplevel_project.directory:
+            self._bst_directory = os.path.join(toplevel_project.directory, ".bst")
+            self._workspaces = self._load_config()
+        else:
+            self._bst_directory = None
+            self._workspaces = {}
+
     # list()
     #
     # Generator function to enumerate workspaces.
diff --git a/tests/format/project.py b/tests/format/project.py
index d3de672..6e06176 100644
--- a/tests/format/project.py
+++ b/tests/format/project.py
@@ -15,10 +15,11 @@ DATA_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "project")
 
 
 @pytest.mark.datafiles(os.path.join(DATA_DIR))
-def test_missing_project_conf(cli, datafiles):
+@pytest.mark.parametrize("args", [["workspace", "list"], ["show", "pony.bst"]], ids=["list-workspace", "show-element"])
+def test_missing_project_conf(cli, datafiles, args):
     project = str(datafiles)
-    result = cli.run(project=project, args=["workspace", "list"])
-    result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.MISSING_PROJECT_CONF)
+    result = cli.run(project=project, args=args)
+    result.assert_main_error(ErrorDomain.STREAM, "project-not-loaded")
 
 
 @pytest.mark.datafiles(os.path.join(DATA_DIR))


[buildstream] 08/08: tests/frontend/artifact_show.py: Test artifact show without a project

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

tvb pushed a commit to branch tristan/optional-project
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit f3366e6932ef709c811ef4cc4d12e737c4df3a7f
Author: Tristan van Berkom <tr...@codethink.co.uk>
AuthorDate: Thu Jan 14 16:21:31 2021 +0900

    tests/frontend/artifact_show.py: Test artifact show without a project
---
 tests/frontend/artifact_show.py | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/tests/frontend/artifact_show.py b/tests/frontend/artifact_show.py
index 392a9e2..2a7131c 100644
--- a/tests/frontend/artifact_show.py
+++ b/tests/frontend/artifact_show.py
@@ -88,7 +88,8 @@ def test_artifact_show_element_missing_deps(cli, tmpdir, datafiles):
 
 # Test artifact show with artifact ref
 @pytest.mark.datafiles(DATA_DIR)
-def test_artifact_show_artifact_ref(cli, tmpdir, datafiles):
+@pytest.mark.parametrize("with_project", [True, False], ids=["with-project", "without-project"])
+def test_artifact_show_artifact_name(cli, tmpdir, datafiles, with_project):
     project = str(datafiles)
     element = "target.bst"
 
@@ -98,6 +99,10 @@ def test_artifact_show_artifact_ref(cli, tmpdir, datafiles):
     cache_key = cli.get_element_key(project, element)
     artifact_ref = "test/target/" + cache_key
 
+    # Delete the project.conf if we're going to try this without a project
+    if not with_project:
+        os.remove(os.path.join(project, "project.conf"))
+
     result = cli.run(project=project, args=["artifact", "show", artifact_ref])
     result.assert_success()
     assert "cached {}".format(artifact_ref) in result.output