Struct libcnb_test::TestContext

source ·
pub struct TestContext<'a> {
    pub pack_stdout: String,
    pub pack_stderr: String,
    pub config: BuildConfig,
    /* private fields */
}
Expand description

Context for a currently executing test.

Fields§

§pack_stdout: String

Standard output of pack, interpreted as an UTF-8 string.

§pack_stderr: String

Standard error of pack, interpreted as an UTF-8 string.

§config: BuildConfig

The configuration used for this integration test.

Implementations§

source§

impl<'a> TestContext<'a>

source

pub fn start_container<C: Borrow<ContainerConfig>, F: FnOnce(ContainerContext)>( &self, config: C, f: F )

Starts a detached container using the provided ContainerConfig.

After the passed function has returned, the Docker container is removed.

If you wish to run a shell command and don’t need to customise the configuration, use the convenience function TestContext::run_shell_command instead.

§Examples
use libcnb_test::{BuildConfig, ContainerConfig, TestRunner};

TestRunner::default().build(
    BuildConfig::new("heroku/builder:22", "tests/fixtures/app"),
    |context| {
        // Start the container using the default process-type:
        // https://buildpacks.io/docs/app-developer-guide/run-an-app/#default-process-type
        context.start_container(ContainerConfig::new(), |container| {
            // ...
        });

        // Start the container using the specified process-type:
        // https://buildpacks.io/docs/app-developer-guide/run-an-app/#non-default-process-type
        context.start_container(ContainerConfig::new().entrypoint("worker"), |container| {
            // ...
        });

        // Start the container using the specified process-type and additional arguments:
        // https://buildpacks.io/docs/app-developer-guide/run-an-app/#non-default-process-type-with-additional-arguments
        context.start_container(
            ContainerConfig::new()
                .entrypoint("another-process")
                .command(["--additional-arg"]),
            |container| {
                // ...
            },
        );

        // Start the container using the provided bash script:
        // https://buildpacks.io/docs/app-developer-guide/run-an-app/#user-provided-shell-process-with-bash-script
        // Only use this shell command form if you need to customise the `ContainerConfig`,
        // otherwise use the convenience function `TestContext::run_shell_command` instead.
        context.start_container(
            ContainerConfig::new()
                .entrypoint("launcher")
                .command(["for i in {1..3}; do echo \"${i}\"; done"]),
            |container| {
                // ...
            },
        );
    },
);
§Panics

Panics if there was an error starting the container, such as when the specified entrypoint/command can’t be found.

Note: Does not panic if the container exits after starting (including if it crashes and exits non-zero).

source

pub fn run_shell_command(&self, command: impl Into<String>) -> LogOutput

Run the provided shell command.

The CNB launcher will run the provided command using bash.

Note: This method will block until the container stops.

§Example
use libcnb_test::{BuildConfig, ContainerConfig, TestRunner};

TestRunner::default().build(
    BuildConfig::new("heroku/builder:22", "tests/fixtures/app"),
    |context| {
        // ...
        let command_output =
            context.run_shell_command("for i in {1..3}; do echo \"${i}\"; done");
        assert_eq!(command_output.stdout, "1\n2\n3\n");
    },
);

This is a convenience function for running shell commands inside the image, that is roughly equivalent to:

use libcnb_test::{BuildConfig, ContainerConfig, TestRunner};

TestRunner::default().build(
    BuildConfig::new("heroku/builder:22", "tests/fixtures/app"),
    |context| {
        // ...
        context.start_container(
            ContainerConfig::new()
                .entrypoint("launcher")
                .command(["for i in {1..3}; do echo \"${i}\"; done"]),
            |container| {
                let log_output = container.logs_wait();
                // ...
            },
        );
    },
);

However, in addition to requiring less boilerplate, run_shell_command is also able to validate the exit status of the container, so should be used instead of start_container where possible.

§Panics

Panics if there was an error starting the container, or the command exited with a non-zero exit code.

source

pub fn download_sbom_files<R, F: Fn(SbomFiles) -> R>(&self, f: F) -> R

Downloads SBOM files from the built image into a temporary directory.

References to the downloaded files are passed into the given function and will be cleaned-up after the function exits.

§Example
use libcnb_data::buildpack_id;
use libcnb_data::sbom::SbomFormat;
use libcnb_test::{BuildConfig, ContainerConfig, SbomType, TestRunner};

TestRunner::default().build(
    BuildConfig::new("heroku/builder:22", "tests/fixtures/app"),
    |context| {
        context.download_sbom_files(|sbom_files| {
            assert!(sbom_files
                .path_for(
                    buildpack_id!("heroku/jvm"),
                    SbomType::Launch,
                    SbomFormat::SyftJson
                )
                .exists());
        });
    },
);
§Panics

Panics if there was an error creating the temporary directory used to store the SBOM files, or if the Pack CLI command used to download the SBOM files failed.

source

pub fn rebuild<C: Borrow<BuildConfig>, F: FnOnce(TestContext<'_>)>( self, config: C, f: F )

Starts a subsequent integration test build.

This function behaves exactly like TestRunner::build, but it will reuse the OCI image from the previous test, causing the CNB lifecycle to restore any cached layers. It will use the same TestRunner as the previous test run.

This function allows testing of subsequent builds, including caching logic and buildpack behaviour when build environment variables change, stacks are upgraded and more.

Note that this function will consume the current context. This is because the image will be changed by the subsequent test, invalidating the context. Running a subsequent test must therefore be the last operation. You can nest subsequent runs if required.

§Example
use libcnb_test::{assert_contains, BuildConfig, TestRunner};

TestRunner::default().build(
    BuildConfig::new("heroku/builder:22", "tests/fixtures/app"),
    |context| {
        assert_contains!(context.pack_stdout, "---> Installing dependencies");

        let config = context.config.clone();
        context.rebuild(config, |context| {
            assert_contains!(context.pack_stdout, "---> Using cached dependencies");
        });
    },
);

Auto Trait Implementations§

§

impl<'a> Freeze for TestContext<'a>

§

impl<'a> !RefUnwindSafe for TestContext<'a>

§

impl<'a> !Send for TestContext<'a>

§

impl<'a> !Sync for TestContext<'a>

§

impl<'a> Unpin for TestContext<'a>

§

impl<'a> !UnwindSafe for TestContext<'a>

Blanket Implementations§

source§

impl<T> Any for T
where T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for T
where T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

source§

impl<T, U> Into<U> for T
where U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

source§

impl<T> IntoEither for T

source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
source§

impl<T> Pointable for T

source§

const ALIGN: usize = _

The alignment of pointer.
§

type Init = T

The type for initializers.
source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.