Skip to content

Commit

Permalink
Merge pull request #5 from US-JOET/demo-automated-tests
Browse files Browse the repository at this point in the history
Provide a demo of EVerest's existing automated testing capabilities
  • Loading branch information
shankari authored Dec 6, 2023
2 parents e055182 + 954b5d2 commit 4619ac2
Show file tree
Hide file tree
Showing 8 changed files with 228 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
@@ -1 +1 @@
TAG=0.0.5
TAG=0.0.6
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ The use cases supported by the three demos are summarized in conceptual block di
- 🚨 AC Charging ⚡: `curl https://raw.githubusercontent.com/everest/everest-demo/main/demo-ac.sh | bash`
- 🚨 ISO 15118 DC Charging ⚡: `curl https://raw.githubusercontent.com/everest/everest-demo/main/demo-iso15118-2-dc.sh | bash`
- 🚨 Two EVSE Charging ⚡: `curl https://raw.githubusercontent.com/everest/everest-demo/main/demo-two-evse.sh | bash`
- 🚨 E2E Automated Tests ⚡: `curl https://raw.githubusercontent.com/everest/everest-demo/main/demo-automated-testing.sh | bash`

### STEP 2: Interact with the demo
- Open the `nodered` flows to understand the module flows at http://127.0.0.1:1880
Expand Down
34 changes: 34 additions & 0 deletions demo-automated-testing.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/usr/bin/env bash

DEMO_COMPOSE_FILE_NAME='docker-compose.automated-tests.yml'
DEMO_DIR="$(mktemp -d)"

delete_temporary_directory() { rm -rf "${DEMO_DIR}"; }
trap delete_temporary_directory EXIT

if [[ ! "${DEMO_DIR}" || ! -d "${DEMO_DIR}" ]]; then
echo 'Error: Failed to create a temporary directory for the demo.'
exit 1
fi

download_demo_file() {
local -r repo_file_path="$1"
local -r repo_raw_url='https://raw.githubusercontent.com/everest/everest-demo/main'
local -r destination_path="${DEMO_DIR}/${repo_file_path}"

mkdir -p "$(dirname ${destination_path})"
curl -s -o "${destination_path}" "${repo_raw_url}/${repo_file_path}"
if [[ "$?" != 0 ]]; then
echo "Error: Failed to retrieve \"${repo_file_path}\" from the demo"
echo 'repository. If this issue persists, please report this as an'
echo 'issue in the EVerest project:'
echo ' https://github.com/EVerest/EVerest/issues'
exit 1
fi
}

download_demo_file "${DEMO_COMPOSE_FILE_NAME}"
download_demo_file .env

docker compose --project-name everest-ac-demo \
--file "${DEMO_DIR}/${DEMO_COMPOSE_FILE_NAME}" up
18 changes: 18 additions & 0 deletions docker-compose.automated-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
version: "3.6"

services:
mqtt-server:
image: ghcr.io/us-joet/everest-demo/mqtt-server:${TAG}
logging:
driver: none

manager:
image: ghcr.io/us-joet/everest-demo/manager:${TAG}
depends_on:
- mqtt-server
environment:
- MQTT_SERVER_ADDRESS=mqtt-server
working_dir: /ext/source/tests
entrypoint: "sh ./run-test.sh"
sysctls:
- net.ipv6.conf.all.disable_ipv6=0
7 changes: 5 additions & 2 deletions manager/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ RUN git clone https://github.com/EVerest/everest-core.git \
# Don't run the test-and-install script since it deletes the build directory!
&& /entrypoint.sh run-script install

# Copy over the custom config *after* the compile
# Copy over the custom config *after* compilation and installation
COPY config-docker.json ./dist/share/everest/modules/OCPP/config-docker.json

COPY user-config/ /ext/source/config/user-config/
# TODO: This should be removed once added to everest-core
COPY ./tests/startup_tests.py /ext/source/tests/core_tests/startup_tests.py

COPY run-test.sh /ext/source/tests/run-test.sh

LABEL org.opencontainers.image.source=https://github.com/everest/everest-demo
3 changes: 3 additions & 0 deletions manager/run-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#! /bin/sh

pytest --everest-prefix /workspace/dist core_tests/startup_tests.py
165 changes: 165 additions & 0 deletions manager/tests/startup_tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
#!/usr/bin/env python
# SPDX-License-Identifier: Apache-2.0
# Copyright 2023 Contributors to EVerest

import logging
import pytest
import queue
import threading
import time

from everest.framework import Module, RuntimeSession
from everest.testing.core_utils.everest_core import EverestCore, Requirement
from everest.testing.core_utils.fixtures import *


class ProbeModule:
def __init__(self, session: RuntimeSession):
m = Module("probe", session)
self._setup = m.say_hello()

# subscribe to session events
evse_manager_ff = self._setup.connections["connector_1"][0]
m.subscribe_variable(
evse_manager_ff, "session_event", self._handle_evse_manager_event
)

self._msg_queue = queue.Queue()

self._ready_event = threading.Event()
self._mod = m
m.init_done(self._ready)

def _ready(self):
self._ready_event.set()

def _handle_evse_manager_event(self, args):
self._msg_queue.put(args)

def _parse_event_dict(self, expected_events: dict, args: dict) -> bool:
keys = expected_events[0].keys()
if len(keys) == 1:
key = list(keys)[0]
if key == "error":
expected_error_code = expected_events[0][key]
error_code = args[key]["error_code"]
return expected_error_code == error_code
else:
logging.error(f"Key: {key} not supported")
else:
logging.warning("Not supporting multiple keys")

def _pool_for_expected_events(
self, expected_events: list[str], end_of_time: float
) -> bool:
while len(expected_events) > 0:
time_left = end_of_time - time.time()

if time_left < 0:
return False
try:
args = self._msg_queue.get(timeout=time_left)
if type(expected_events[0]) is str:
if expected_events[0] == args["event"]:
expected_events.pop(0)
elif type(expected_events[0]) is dict:
if self._parse_event_dict(expected_events, args):
expected_events.pop(0)
except queue.Empty:
return False

return True

def test(
self, charging_session_cmd: dict, expected_events: list[str], timeout: float
) -> bool:
end_of_time = time.time() + timeout

if not self._ready_event.wait(timeout):
return False

# fetch fulfillment
car_sim_ff = self._setup.connections["test_control"][0]

# enable simulator
self._mod.call_command(car_sim_ff, "enable", {"value": True})

# start charging simulation
logging.info(charging_session_cmd["value"])
self._mod.call_command(
car_sim_ff, "executeChargingSession", charging_session_cmd
)

return self._pool_for_expected_events(expected_events, end_of_time)


@pytest.mark.asyncio
async def test_000_startup_check(everest_core: EverestCore):
logging.info(">>>>>>>>> test_000_startup_check <<<<<<<<<")
everest_core.start()


@pytest.mark.everest_core_config("config-sil.yaml")
@pytest.mark.asyncio
async def test_001_start_test_module(everest_core: EverestCore):
logging.info(">>>>>>>>> test_001_start_test_module <<<<<<<<<")

test_connections = {
"test_control": [Requirement("car_simulator", "main")],
"connector_1": [Requirement("connector_1", "evse")],
}

everest_core.start(standalone_module="probe", test_connections=test_connections)
logging.info("everest-core ready, waiting for probe module")

session = RuntimeSession(
str(everest_core.prefix_path), str(everest_core.everest_config_path)
)

probe = ProbeModule(session)

if everest_core.status_listener.wait_for_status(10, ["ALL_MODULES_STARTED"]):
everest_core.all_modules_started_event.set()
logging.info("set all modules started event...")
charging_session_cmd = {
"value": "sleep 1;iec_wait_pwr_ready;sleep 1;draw_power_regulated 16,3;sleep 5"
}
expected_events = ["TransactionStarted", "ChargingStarted"]

assert probe.test(charging_session_cmd, expected_events, 20)


@pytest.mark.asyncio
async def test_000_demo_run(everest_core: EverestCore):
logging.info(">>>>>>>>> test_000_demo_run <<<<<<<<<")

test_connections = {
"test_control": [Requirement("car_simulator", "main")],
"connector_1": [Requirement("connector_1", "evse")],
}

everest_core.start(standalone_module="probe", test_connections=test_connections)

logging.info("everest-core ready, waiting for probe module")

session = RuntimeSession(
str(everest_core.prefix_path), str(everest_core.everest_config_path)
)

probe = ProbeModule(session)

if everest_core.status_listener.wait_for_status(10, ["ALL_MODULES_STARTED"]):
everest_core.all_modules_started_event.set()
logging.info("set all modules started event...")

charging_session_cmd = {
"value": "sleep 1;iec_wait_pwr_ready;sleep 1;draw_power_regulated 16,3;sleep 5;pause;sleep 5;diode_fail;sleep 5"
}
expected_events = [
"TransactionStarted",
"ChargingStarted",
"ChargingPausedEV",
{"error": "CarDiodeFault"},
]

assert probe.test(charging_session_cmd, expected_events, 20)
2 changes: 1 addition & 1 deletion nodered/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ RUN npm install node-red-contrib-ui-actions
RUN npm install node-red-node-ui-table
RUN npm install node-red-contrib-ui-level

COPY config /config
COPY --chown=node-red:root config /config

LABEL org.opencontainers.image.source=https://github.com/everest/everest-demo

0 comments on commit 4619ac2

Please sign in to comment.