Commit b4105e8d authored by Tristan Van Berkom's avatar Tristan Van Berkom

doc: Adding part 3 of the getting started tutorial: autotools element

This part of the tutorial uses a lot of the work from Phil Dawson
and James Ennis, and uses their example submitted on merge request
499 as a basis to introduce the user to yaml composition and variable
resolution.

This is a part of issue #103
parent 72fbaa1c
kind: stack
description: Base stack
depends:
- base/alpine.bst
kind: import
description: |
Alpine Linux base runtime
sources:
- kind: tar
# This is a post doctored, trimmed down system image
# of the Alpine linux distribution.
#
url: alpine:integration-tests-base.v1.x86_64.tar.xz
ref: 3eb559250ba82b64a68d86d0636a6b127aa5f6d25d3601a79f79214dc9703639
kind: autotools
description: |
Hello world example from automake
variables:
# The hello world example lives in the doc/amhello folder.
#
# Set the %{command-subdir} variable to that location
# and just have the autotools element run it's commands there.
#
command-subdir: doc/amhello
sources:
- kind: tar
url: gnu:automake-1.16.tar.gz
ref: 80da43bb5665596ee389e6d8b64b4f122ea4b92a685b1dbd813cd1f0e0c2d83f
depends:
- base.bst
# Unique project name
name: running-commands
# Required BuildStream format version
format-version: 9
# Subdirectory where elements are stored
element-path: elements
# Define an alias for our alpine tarball
aliases:
alpine: https://gnome7.codethink.co.uk/tarballs/
gnu: https://ftp.gnu.org/gnu/automake/
commands:
# Make it fetch first
- directory: ../examples/autotools
command: fetch hello.bst
# Capture a `bst show` of the variables
- directory: ../examples/autotools
output: ../source/sessions/autotools-show-variables.html
command: show --deps none --format "%{vars}" hello.bst
# Capture a `bst build`
- directory: ../examples/autotools
output: ../source/sessions/autotools-build.html
command: build hello.bst
# Capture a shell output
- directory: ../examples/autotools
output: ../source/sessions/autotools-shell.html
command: shell hello.bst -- hello
This diff is collapsed.
<!--
WARNING: This file was generated with bst2html.py
-->
<div class="highlight" style="font-size:x-small"><pre>
<span style="color:#C4A000;font-weight:bold">user@host</span>:<span style="color:#3456A4;font-weight:bold">~/autotools</span>$ bst shell hello.bst -- hello
<span style="color:#06989A"><span style="opacity:0.5">[</span></span><span style="color:#C4A000">--</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">--</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">--</span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#06989A"><span style="opacity:0.5">] </span></span><span style="color:#3465A4"><span style=""><span style="opacity:0.5">START </span></span></span><span style="color:#06989A"><span style="opacity:0.5"> </span></span>Loading pipeline
<span style="color:#06989A"><span style="opacity:0.5">[</span></span><span style="color:#C4A000">00</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">00</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">00</span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#06989A"><span style="opacity:0.5">] </span></span><span style="color:#4E9A06"><span style=""><span style="opacity:0.5">SUCCESS</span></span></span><span style="color:#06989A"><span style="opacity:0.5"> </span></span>Loading pipeline
<span style="color:#06989A"><span style="opacity:0.5">[</span></span><span style="color:#C4A000">--</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">--</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">--</span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#06989A"><span style="opacity:0.5">] </span></span><span style="color:#3465A4"><span style=""><span style="opacity:0.5">START </span></span></span><span style="color:#06989A"><span style="opacity:0.5"> </span></span>Resolving pipeline
<span style="color:#06989A"><span style="opacity:0.5">[</span></span><span style="color:#C4A000">00</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">00</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">00</span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#06989A"><span style="opacity:0.5">] </span></span><span style="color:#4E9A06"><span style=""><span style="opacity:0.5">SUCCESS</span></span></span><span style="color:#06989A"><span style="opacity:0.5"> </span></span>Resolving pipeline
<span style="color:#06989A"><span style="opacity:0.5">[</span></span><span style="color:#C4A000">--</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">--</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">--</span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#06989A"><span style="opacity:0.5">] </span></span><span style="color:#3465A4"><span style=""><span style="opacity:0.5">START </span></span></span><span style="color:#06989A"><span style="opacity:0.5"> </span></span>Resolving cached state
<span style="color:#06989A"><span style="opacity:0.5">[</span></span><span style="color:#C4A000">00</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">00</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">00</span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#06989A"><span style="opacity:0.5">] </span></span><span style="color:#4E9A06"><span style=""><span style="opacity:0.5">SUCCESS</span></span></span><span style="color:#06989A"><span style="opacity:0.5"> </span></span>Resolving cached state
<span style="color:#06989A"><span style="opacity:0.5">[</span></span><span style="color:#C4A000">--</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">--</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">--</span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#06989A"><span style="opacity:0.5">] </span></span><span style="color:#3465A4"><span style=""><span style="opacity:0.5">START </span></span></span><span style="color:#06989A"><span style="opacity:0.5"> </span></span>Staging dependencies
<span style="color:#06989A"><span style="opacity:0.5">[</span></span><span style="color:#C4A000">00</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">00</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">00</span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#06989A"><span style="opacity:0.5">] </span></span><span style="color:#4E9A06"><span style=""><span style="opacity:0.5">SUCCESS</span></span></span><span style="color:#06989A"><span style="opacity:0.5"> </span></span>Staging dependencies
<span style="color:#06989A"><span style="opacity:0.5">[</span></span><span style="color:#C4A000">--</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">--</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">--</span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#06989A"><span style="opacity:0.5">] </span></span><span style="color:#3465A4"><span style=""><span style="opacity:0.5">START </span></span></span><span style="color:#06989A"><span style="opacity:0.5"> </span></span>Integrating sandbox
<span style="color:#06989A"><span style="opacity:0.5">[</span></span><span style="color:#C4A000">00</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">00</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">00</span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#06989A"><span style="opacity:0.5">] </span></span><span style="color:#4E9A06"><span style=""><span style="opacity:0.5">SUCCESS</span></span></span><span style="color:#06989A"><span style="opacity:0.5"> </span></span>Integrating sandbox
<span style="color:#06989A"><span style="opacity:0.5">[</span></span><span style="color:#C4A000">--</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">--</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">--</span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#C4A000">3f7f32d9</span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#C4A000"> main</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">hello.bst </span><span style="color:#06989A"><span style="opacity:0.5">] </span></span><span style="color:#06989A"><span style=""><span style="opacity:0.5">STATUS </span></span></span><span style="color:#06989A"><span style="opacity:0.5"> </span></span>Running command
<span style="opacity:0.5"> hello</span>
Hello World!
This is amhello 1.0.
</pre></div>
<!--
WARNING: This file was generated with bst2html.py
-->
<div class="highlight" style="font-size:x-small"><pre>
<span style="color:#C4A000;font-weight:bold">user@host</span>:<span style="color:#3456A4;font-weight:bold">~/autotools</span>$ bst show --deps none --format "%{vars}" hello.bst
<span style="color:#06989A"><span style="opacity:0.5">[</span></span><span style="color:#C4A000">--</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">--</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">--</span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#06989A"><span style="opacity:0.5">] </span></span><span style="color:#3465A4"><span style=""><span style="opacity:0.5">START </span></span></span><span style="color:#06989A"><span style="opacity:0.5"> </span></span>Loading pipeline
<span style="color:#06989A"><span style="opacity:0.5">[</span></span><span style="color:#C4A000">00</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">00</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">00</span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#06989A"><span style="opacity:0.5">] </span></span><span style="color:#4E9A06"><span style=""><span style="opacity:0.5">SUCCESS</span></span></span><span style="color:#06989A"><span style="opacity:0.5"> </span></span>Loading pipeline
<span style="color:#06989A"><span style="opacity:0.5">[</span></span><span style="color:#C4A000">--</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">--</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">--</span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#06989A"><span style="opacity:0.5">] </span></span><span style="color:#3465A4"><span style=""><span style="opacity:0.5">START </span></span></span><span style="color:#06989A"><span style="opacity:0.5"> </span></span>Resolving pipeline
<span style="color:#06989A"><span style="opacity:0.5">[</span></span><span style="color:#C4A000">00</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">00</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">00</span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#06989A"><span style="opacity:0.5">] </span></span><span style="color:#4E9A06"><span style=""><span style="opacity:0.5">SUCCESS</span></span></span><span style="color:#06989A"><span style="opacity:0.5"> </span></span>Resolving pipeline
<span style="color:#06989A"><span style="opacity:0.5">[</span></span><span style="color:#C4A000">--</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">--</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">--</span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#06989A"><span style="opacity:0.5">] </span></span><span style="color:#3465A4"><span style=""><span style="opacity:0.5">START </span></span></span><span style="color:#06989A"><span style="opacity:0.5"> </span></span>Resolving cached state
<span style="color:#06989A"><span style="opacity:0.5">[</span></span><span style="color:#C4A000">00</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">00</span><span style="color:#06989A"><span style="opacity:0.5">:</span></span><span style="color:#C4A000">00</span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#06989A"><span style="opacity:0.5">][</span></span><span style="color:#06989A"><span style="opacity:0.5">] </span></span><span style="color:#4E9A06"><span style=""><span style="opacity:0.5">SUCCESS</span></span></span><span style="color:#06989A"><span style="opacity:0.5"> </span></span>Resolving cached state
autogen: "export NOCONFIGURE=1;\n\nif [ -x ./configure ]; then true;\nelif [ -x autogen\
\ ]; then ./autogen;\nelif [ -x autogen.sh ]; then ./autogen.sh;\nelif [ -x bootstrap\
\ ]; then ./bootstrap;\nelif [ -x bootstrap.sh ]; then ./bootstrap.sh;\nelse autoreconf\
\ -ivf;\nfi"
bindir: /usr/bin
build-root: /buildstream/running-commands/hello.bst
command-subdir: doc/amhello
conf-args: "--prefix=/usr \\\n--exec-prefix=/usr \\\n--bindir=/usr/bin \\\n--sbindir=/usr/sbin\
\ \\\n--sysconfdir=/etc \\\n--datadir=/usr/share \\\n--includedir=/usr/include \\\
\n--libdir=/usr/lib \\\n--libexecdir=/usr/libexec \\\n--localstatedir=/var \\\n\
--sharedstatedir=/usr/com \\\n--mandir=/usr/share/man \\\n--infodir=/usr/share/info"
conf-cmd: ./configure
conf-extra: ''
conf-global: ''
conf-local: ''
configure: "./configure --prefix=/usr \\\n--exec-prefix=/usr \\\n--bindir=/usr/bin\
\ \\\n--sbindir=/usr/sbin \\\n--sysconfdir=/etc \\\n--datadir=/usr/share \\\n--includedir=/usr/include\
\ \\\n--libdir=/usr/lib \\\n--libexecdir=/usr/libexec \\\n--localstatedir=/var \\\
\n--sharedstatedir=/usr/com \\\n--mandir=/usr/share/man \\\n--infodir=/usr/share/info"
datadir: /usr/share
debugdir: /usr/lib/debug
docdir: /usr/share/doc
element-name: hello.bst
exec_prefix: /usr
fix-pyc-timestamps: "find \"/buildstream-install\" -name '*.pyc' -exec \\\n dd if=/dev/zero\
\ of={} bs=1 count=4 seek=4 conv=notrunc ';'"
includedir: /usr/include
infodir: /usr/share/info
install-root: /buildstream-install
lib: lib
libdir: /usr/lib
libexecdir: /usr/libexec
localstatedir: /var
make: make
make-install: make -j1 DESTDIR="/buildstream-install" install
mandir: /usr/share/man
max-jobs: '8'
objcopy-extract-args: --only-keep-debug --compress-debug-sections
objcopy-link-args: --add-gnu-debuglink
prefix: /usr
project-name: running-commands
sbindir: /usr/sbin
sharedstatedir: /usr/com
strip-args: --remove-section=.comment --remove-section=.note --strip-unneeded
strip-binaries: "find \"/buildstream-install\" -type f \\\n '(' -perm -111 -o -name\
\ '*.so*' \\\n -o -name '*.cmxs' -o -name '*.node' ')' \\\n -exec sh -ec \\\
\n 'read -n4 hdr <\"$1\" # check for elf header\n if [ \"$hdr\" != \"$(printf\
\ \\\\x7fELF)\" ]; then\n exit 0\n fi\n debugfile=\"/buildstream-install/usr/lib/debug/$(basename\
\ \"$1\")\"\n mkdir -p \"$(dirname \"$debugfile\")\"\n objcopy --only-keep-debug\
\ --compress-debug-sections \"$1\" \"$debugfile\"\n chmod 644 \"$debugfile\"\n\
\ strip --remove-section=.comment --remove-section=.note --strip-unneeded \"$1\"\
\n objcopy --add-gnu-debuglink \"$debugfile\" \"$1\"' - {} ';'"
sysconfdir: /etc
</pre></div>
Using the autotools element
===========================
In :ref:`the last chapter <tutorial_running_commands>` we observed how the
:mod:`manual <elements.manual>` element works, allowing one to specify and
run commands manually in the process of constructing an *artifact*.
In this section, we'll go over a mostly automated build of a similar
hello world example. We will observe how our configurations of the
:mod:`autotools <elements.autotools>` element translate to configurations
on the :mod:`manual <elements.manual>` element, and observe how
:ref:`variable substitution <format_variables>` works.
.. note::
This example is distributed with BuildStream
in the `doc/examples/autotools
<https://gitlab.com/BuildStream/buildstream/tree/master/doc/examples/autotools>`_
subdirectory.
Overview
--------
Instead of using the :mod:`local <sources.local>` source as we have been using
in the previous examples, we're going to use a :mod:`tar <sources.tar>` source
this time to obtain the ``automake`` release tarball directly from the upstream
hosting.
In this example we're going to build the example program included in the
upstream ``automake`` tarball itself, and we're going to use the automated
:mod:`autotools <elements.autotools>` build element to do so.
Project structure
-----------------
``project.conf``
~~~~~~~~~~~~~~~~
.. literalinclude:: ../../examples/autotools/project.conf
:language: yaml
Like the :ref:`last project.conf <tutorial_running_commands_project_conf>`, we've
added another :ref:`source alias <project_source_aliases>` for ``gnu``, the location
from which we're going to download the ``automake`` tarball.
``elements/base/alpine.bst`` and ``elements/base.bst``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The alpine base and base stack element are defined in the
same way as in the last chapter: :ref:`tutorial_running_commands`.
``elements/hello.bst``
~~~~~~~~~~~~~~~~~~~~~~
.. literalinclude:: ../../examples/autotools/elements/hello.bst
:language: yaml
In this case, we haven't touched the element's ``config`` section
at all, instead we just slightly override the bahavior of the
:mod:`autotools <elements.autotools>` build element by overriding
the :ref:`command-subdir variable <format_variables>`
Looking at variables
''''''''''''''''''''
Let's take a moment and observe how :ref:`element composition
<format_composition>` works with variables.
As :ref:`the documentation <format_composition>` mentions:
* The initial settings of the ``project.conf`` variables are setup
using BuildStream's :ref:`builtin defaults <project_builtin_defaults>`.
* After this, your local ``project.conf`` may override some variables
on a project wide basis. Those will in turn be overridden by any
defaults provided by element classes, such as the variables set in the
documentation of the :mod:`autotools <elements.autotools>` build element.
The variables you set in your final ``<element.bst>`` *element declarations*,
will have the final say on the value of a particular variable.
* Finally, the variables, which may be composed of other variables,
are resolved after all composition has taken place.
The variable we needed to override was ``command-subdir``, which is an
automatic variable provided by the :mod:`BuildElement <buildstream.buildelement>`
abstract class. This variable simply instructs the :mod:`BuildElement <buildstream.buildelement>`
in which subdirectory of the ``%{build-root}`` to run it's commands in.
One can always display the resolved set of variables for a given
element's configuration using :ref:`bst show <invoking_show>`:
.. raw:: html
:file: ../sessions/autotools-show-variables.html
As an exercise, we suggest that you modify the ``hello.bst``
element to set the prefix like so:
.. code:: yaml
variables:
prefix: "/opt"
And rerun the above :ref:`bst show <invoking_show>` command to observe how this
changes the output.
Observe where the variables are declared in the :ref:`builtin defaults
<project_builtin_defaults>` and :mod:`autotools <elements.autotools>` element
documentation, and how overriding these affects the resolved set of variables.
Using the project
-----------------
Build the hello.bst element
~~~~~~~~~~~~~~~~~~~~~~~~~~~
To build the project, run :ref:`bst build <invoking_build>` in the
following way:
.. raw:: html
:file: ../sessions/autotools-build.html
Run the hello world program
~~~~~~~~~~~~~~~~~~~~~~~~~~~
We probably know by now what's going to happen, but let's run
the program we've compiled anyway using :ref:`bst shell <invoking_shell>`:
.. raw:: html
:file: ../sessions/autotools-shell.html
Summary
-------
Now we've used a builtin :ref:`build element <plugins_build_elements>`, and
we've taken a look into :ref:`how variables work <format_variables>`.
When browsing the :ref:`build elements <plugins_build_elements>` in the
documentation, we are now equipped with a good idea of what an element is going
to do, based on their default YAML configuration and any configurations
we have in our project. We can also now observe what variables are in effect
for the build of a given element, using :ref:`bst show <invoking_show>`.
......@@ -10,3 +10,4 @@ projects.
tutorial/first-project
tutorial/running-commands
tutorial/autotools
import os
import pytest
from tests.testutils import cli_integration as cli
from tests.testutils.integration import assert_contains
from tests.testutils.site import IS_LINUX
pytestmark = pytest.mark.integration
DATA_DIR = os.path.join(
os.path.dirname(os.path.realpath(__file__)), '..', '..', 'doc', 'examples', 'autotools'
)
# Tests a build of the autotools amhello project on a alpine-linux base runtime
@pytest.mark.skipif(not IS_LINUX, reason='Only available on linux')
@pytest.mark.datafiles(DATA_DIR)
def test_autotools_build(cli, tmpdir, datafiles):
project = os.path.join(datafiles.dirname, datafiles.basename)
checkout = os.path.join(cli.directory, 'checkout')
# Check that the project can be built correctly.
result = cli.run(project=project, args=['build', 'hello.bst'])
result.assert_success()
result = cli.run(project=project, args=['checkout', 'hello.bst', checkout])
result.assert_success()
assert_contains(checkout, ['/usr', '/usr/lib', '/usr/bin',
'/usr/share', '/usr/lib/debug',
'/usr/lib/debug/hello', '/usr/bin/hello',
'/usr/share/doc', '/usr/share/doc/amhello',
'/usr/share/doc/amhello/README'])
# Test running an executable built with autotools.
@pytest.mark.skipif(not IS_LINUX, reason='Only available on linux')
@pytest.mark.datafiles(DATA_DIR)
def test_autotools_run(cli, tmpdir, datafiles):
project = os.path.join(datafiles.dirname, datafiles.basename)
result = cli.run(project=project, args=['build', 'hello.bst'])
result.assert_success()
result = cli.run(project=project, args=['shell', 'hello.bst', 'hello'])
result.assert_success()
assert result.output == 'Hello World!\nThis is amhello 1.0.\n'
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment