Skip to content

Commit

Permalink
feat: Create E2E test infrastructure (#172)
Browse files Browse the repository at this point in the history
This PR aims to create the infrastructure for all E2E Testing done in this project.
Adds the following:
- New Module called Suites -> This aims to separate E2E tests from any implementations.
- Uses JUint 5 as a testing framework.
- Clear test separation, which will allow for easier test plan implementation in the future.
- Uses testcontainers to managa and handle Block Node Application containers
- Include the first test from the test plan #183 

Signed-off-by: georgi-l95 <[email protected]>
  • Loading branch information
georgi-l95 authored Sep 19, 2024
1 parent 9dae530 commit 5ab76fd
Show file tree
Hide file tree
Showing 12 changed files with 495 additions and 2 deletions.
66 changes: 66 additions & 0 deletions .github/workflows/e2e-tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
##
# Copyright (C) 2024 Hedera Hashgraph, LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##
name: "E2E Test Suites"
on:
push:
branches:
- main
- release/*
pull_request:
branches:
- "*"

defaults:
run:
shell: bash

env:
GRADLE_EXEC: ./gradlew

jobs:
e2e-tests:
runs-on: block-node-linux-medium
steps:
- name: Harden Runner
uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
with:
egress-policy: audit

- name: Checkout code
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
fetch-depth: 0

- name: Expand Shallow Clone for Spotless
run: |
if [ -f .git/shallow ]; then
git fetch --unshallow --no-recurse-submodules
else
echo "Repository is not shallow, no need to unshallow."
fi
- name: Set up JDK 21
uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
with:
distribution: 'temurin'
java-version: '21'

- name: Build application
run: ${{ env.GRADLE_EXEC }} build

- name: Run Acceptance Tests
id: acceptance-tests
run: ${GRADLE_EXEC} runSuites
2 changes: 1 addition & 1 deletion .github/workflows/smoke-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ jobs:

- name: Run application in background, capture logs in app.log
run: |
${{ env.GRADLE_EXEC }} run 2> server/src/test/resources/app.log < /dev/null &
${{ env.GRADLE_EXEC }} run -x :suites:run 2> server/src/test/resources/app.log < /dev/null &
echo "Application started with PID $APP_PID"
sleep 10
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,14 @@ extraJavaModuleInfo {
module("org.jetbrains.kotlinx:kotlinx-metadata-jvm", "kotlinx.metadata.jvm")

// Test clients only
module("com.github.docker-java:docker-java-api", "com.github.dockerjava.api")
module("com.github.docker-java:docker-java-transport", "com.github.dockerjava.transport")
module(
"com.github.docker-java:docker-java-transport-zerodep",
"com.github.dockerjava.transport.zerodep"
)
module("org.slf4j:slf4j-api", "org.slf4j") { patchRealModule() }
module("io.github.cdimascio:java-dotenv", "io.github.cdimascio")
module("com.google.protobuf:protobuf-java-util", "com.google.protobuf.util")
module("com.squareup:javapoet", "com.squareup.javapoet") {
exportAllPackages()
Expand Down
25 changes: 25 additions & 0 deletions buildSrc/src/main/kotlin/com.hedera.block.suites.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

plugins {
id("application")
id("com.hedera.block.conventions")
id("me.champeau.jmh")
}

val maven = publishing.publications.create<MavenPublication>("maven") { from(components["java"]) }

signing.sign(maven)
7 changes: 7 additions & 0 deletions gradle/modules.properties
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ org.apache.commons.io=commons-io:commons-io
org.apache.commons.lang3=org.apache.commons:commons-lang3
org.apache.commons.compress=org.apache.commons:commons-compress

org.testcontainers=org.testcontainers:testcontainers
org.testcontainers.junit-jupiter=org.testcontainers:junit-jupiter
com.github.dockerjava.api=com.github.docker-java:docker-java-api
com.github.docker-java.transport.zerodep=com.github.docker-java:docker-java-transport-zerodep
com.github.docker-java.transport.httpclient5=com.github.docker-java:docker-java-transport-httpclient5
io.github.cdimascio=io.github.cdimascio:java-dotenv

java.annotation=javax.annotation:javax.annotation-api
org.apache.logging.log4j.slf4j2.impl=org.apache.logging.log4j:log4j-slf4j2-impl

Expand Down
5 changes: 5 additions & 0 deletions server/docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,10 @@ RUN tar -xvf server-${VERSION}.tar
# Copy the logging properties file
COPY logging.properties logging.properties

# HEALTHCHECK for liveness and readiness
HEALTHCHECK --interval=30s --timeout=10s --start-period=3s --retries=3 \
CMD curl -f http://localhost:8080/healthz/livez || exit 1 && \
curl -f http://localhost:8080/healthz/readyz || exit 1

# RUN the bin script for starting the server
ENTRYPOINT ["/bin/bash", "-c", "/app/server-${VERSION}/bin/server"]
7 changes: 6 additions & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ plugins {
}

// Include the subprojects
include(":suites")
include(":stream")
include(":server")
include(":simulator")
Expand Down Expand Up @@ -95,9 +96,13 @@ dependencyResolutionManagement {
// Testing only versions
version("org.assertj.core", "3.23.1")
version("org.junit.jupiter.api", "5.10.2")
version("org.junit.platform", "1.11.0")
version("org.mockito", "5.8.0")
version("org.mockito.junit.jupiter", "5.8.0")

version("org.testcontainers", "1.20.1")
version("org.testcontainers.junit-jupiter", "1.20.1")
version("com.github.docker-java", "3.4.0")
version("io.github.cdimascio", "5.2.2")
}
}
}
Expand Down
67 changes: 67 additions & 0 deletions suites/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright (C) 2022-2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

plugins {
id("application")
id("com.hedera.block.suites")
}

description = "Hedera Block Node E2E Suites"

application {
mainModule = "com.hedera.block.suites"
mainClass = "com.hedera.block.suites.BaseSuite"
}

mainModuleInfo {
requires("org.junit.jupiter.api")
requires("org.junit.platform.suite.api")
requires("org.testcontainers")
requires("io.github.cdimascio")
runtimeOnly("org.testcontainers.junit-jupiter")
runtimeOnly("org.junit.jupiter.engine")
}

val updateDockerEnv =
tasks.register<Exec>("updateDockerEnv") {
description =
"Creates the .env file in the docker folder that contains environment variables for Docker"
group = "docker"

workingDir(layout.projectDirectory.dir("../server/docker"))
commandLine("./update-env.sh", project.version)
}

// Task to build the Docker image
tasks.register<Exec>("createDockerImage") {
description = "Creates the Docker image of the Block Node Server based on the current version"
group = "docker"

dependsOn(updateDockerEnv, tasks.assemble)
workingDir(layout.projectDirectory.dir("../server/docker"))
commandLine("./docker-build.sh", project.version, layout.projectDirectory.dir("..").asFile)
}

tasks.register<Test>("runSuites") {
description = "Runs E2E Test Suites"
group = "suites"
dependsOn("createDockerImage")

useJUnitPlatform()
testLogging { events("passed", "skipped", "failed") }
testClassesDirs = sourceSets["main"].output.classesDirs
classpath = sourceSets["main"].runtimeClasspath
}
124 changes: 124 additions & 0 deletions suites/src/main/java/com/hedera/block/suites/BaseSuite.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* Copyright (C) 2022-2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.hedera.block.suites;

import io.github.cdimascio.dotenv.Dotenv;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.utility.DockerImageName;

/**
* BaseSuite is an abstract class that provides common setup and teardown functionality for test
* suites using Testcontainers to manage a Docker container for the Block Node server.
*
* <p>This class is responsible for:
*
* <ul>
* <li>Starting a Docker container running the Block Node Application with a specified version.
* <li>Stopping the container after tests have been executed.
* </ul>
*
* <p>The Block Node Application version is retrieved dynamically from an environment file (.env).
*/
public abstract class BaseSuite {

/** Container running the Block Node Application */
protected static GenericContainer<?> blockNodeContainer;

/** Port that is used by the Block Node Application */
protected static int blockNodePort;

/**
* Default constructor for the BaseSuite class.
*
* <p>This constructor can be used by subclasses or the testing framework to initialize the
* BaseSuite. It does not perform any additional setup.
*/
public BaseSuite() {
// No additional setup required
}

/**
* Setup method to be executed before all tests.
*
* <p>This method initializes the Block Node server container using Testcontainers.
*/
@BeforeAll
public static void setup() {
blockNodeContainer = getConfiguration();
blockNodeContainer.start();
}

/**
* Teardown method to be executed after all tests.
*
* <p>This method stops the Block Node server container if it is running. It ensures that
* resources are cleaned up after the test suite execution is complete.
*/
@AfterAll
public static void teardown() {
if (blockNodeContainer != null) {
blockNodeContainer.stop();
}
}

/**
* Retrieves the configuration for the Block Node server container.
*
* <p>This method initializes the Block Node container with the version retrieved from the .env
* file. It configures the container and returns it.
*
* <p>Specific configuration steps include:
*
* <ul>
* <li>Setting the environment variable "VERSION" from the .env file.
* <li>Exposing the default gRPC port (8080).
* <li>Using the Testcontainers health check mechanism to ensure the container is ready.
* </ul>
*
* @return a configured {@link GenericContainer} instance for the Block Node server
*/
public static GenericContainer<?> getConfiguration() {
String blockNodeVersion = BaseSuite.getBlockNodeVersion();
blockNodePort = 8080;
blockNodeContainer =
new GenericContainer<>(
DockerImageName.parse("block-node-server:" + blockNodeVersion))
.withExposedPorts(blockNodePort)
.withEnv("VERSION", blockNodeVersion)
.waitingFor(Wait.forListeningPort())
.waitingFor(Wait.forHealthcheck());
return blockNodeContainer;
}

/**
* Retrieves the Block Node server version from the .env file.
*
* <p>This method loads the .env file from the "../server/docker" directory and extracts the
* value of the "VERSION" environment variable, which represents the version of the Block Node
* server to be used in the container.
*
* @return the version of the Block Node server as a string
*/
private static String getBlockNodeVersion() {
Dotenv dotenv = Dotenv.configure().directory("../server/docker").filename(".env").load();

return dotenv.get("VERSION");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright (C) 2022-2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.hedera.block.suites.grpc;

import com.hedera.block.suites.grpc.negative.NegativeServerAvailabilityTests;
import com.hedera.block.suites.grpc.positive.PositiveServerAvailabilityTests;
import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.Suite;

/**
* Test suite for running gRPC server availability tests, including both positive and negative test
* scenarios.
*
* <p>This suite aggregates the tests from {@link PositiveServerAvailabilityTests} and {@link
* NegativeServerAvailabilityTests}. The {@code @Suite} annotation allows running all selected
* classes in a single test run.
*/
@Suite
@SelectClasses({PositiveServerAvailabilityTests.class, NegativeServerAvailabilityTests.class})
public class GrpcTestSuites {

/**
* Default constructor for the {@link GrpcTestSuites} class. This constructor is empty as it
* does not need to perform any initialization.
*/
public GrpcTestSuites() {}
}
Loading

0 comments on commit 5ab76fd

Please sign in to comment.