Skip to content

Commit

Permalink
VS Code Dev Container (dev & test environment) (#605)
Browse files Browse the repository at this point in the history
* build: dev container

Add a VS Code Dev Container from the blueprint at https://github.com/ludeeus/integration_blueprint/tree/bceaae212fefefae84d9529cde5cb6f4b60cc865

* build: dev container test setup

Add support for unit testing in the dev container environment with debugging and code coverage.

* ci: adjust to dev container test restructuring

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* ci: fix coverage collection

* build: add dummy light to HA config

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* build: update dev container to Python 3.11 (for HA 2023.6)

* Add VS Code tasks

* Use pre-commit hooks for linting

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Unpin HA version

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Bas Nijholt <basnijholt@gmail.com>
  • Loading branch information
3 people authored Jun 11, 2023
1 parent 12dae4b commit 6283158
Show file tree
Hide file tree
Showing 18 changed files with 329 additions and 111 deletions.
42 changes: 42 additions & 0 deletions .devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "basnijholt/adaptive_lighting",
"image": "mcr.microsoft.com/vscode/devcontainers/python:0-3.11-bullseye",
"postCreateCommand": "scripts/setup",
"forwardPorts": [
8123
],
"portsAttributes": {
"8123": {
"label": "Home Assistant",
"onAutoForward": "notify"
}
},
"customizations": {
"vscode": {
"extensions": [
"ms-python.python",
"github.vscode-pull-request-github",
"ryanluker.vscode-coverage-gutters",
"ms-python.vscode-pylance"
],
"settings": {
"files.eol": "\n",
"editor.tabSize": 4,
"python.pythonPath": "/usr/bin/python3",
"python.analysis.autoSearchPaths": false,
"python.linting.pylintEnabled": true,
"python.linting.enabled": true,
"python.formatting.provider": "black",
"python.formatting.blackPath": "/usr/local/py-utils/bin/black",
"editor.formatOnPaste": false,
"editor.formatOnSave": true,
"editor.formatOnType": true,
"files.trimTrailingWhitespace": true
}
}
},
"remoteUser": "vscode",
"features": {
"ghcr.io/devcontainers/features/rust:1": {}
}
}
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text=auto eol=lf
10 changes: 4 additions & 6 deletions .github/workflows/pytest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,6 @@ jobs:
run: |
cd core
# Link homeassitant.components.adaptive_lighting
cd homeassistant/components
ln -fs ../../../custom_components/adaptive_lighting adaptive_lighting
cd -
# Link adaptive_lighting tests
cd tests/components/
ln -fs ../../../tests adaptive_lighting
Expand All @@ -63,14 +58,17 @@ jobs:
- name: Run pytest
timeout-minutes: 60
run: |
export PYTHONPATH=${PYTHONPATH}:${PWD}
cd core
python3 -X dev -m pytest \
-vvv \
-qq \
--timeout=9 \
--durations=10 \
--cov="homeassistant" \
--cov="custom_components.adaptive_lighting" \
--cov-report=xml \
-o console_output_style=count \
-p no:sugar \
tests/components/adaptive_lighting
env:
HA_CLONE: true
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,11 @@ dmypy.json

# Pyre type checker
.pyre/

# IDEs
.vscode
.idea

# Home Assistant configuration
config/*
!config/configuration.yaml
48 changes: 48 additions & 0 deletions .ruff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# The contents of this file is based on https://github.com/home-assistant/core/blob/dev/pyproject.toml

target-version = "py310"

select = [
"B007", # Loop control variable {name} not used within loop body
"B014", # Exception handler with duplicate exception
"C", # complexity
"D", # docstrings
"E", # pycodestyle
"F", # pyflakes/autoflake
"ICN001", # import concentions; {name} should be imported as {asname}
"PGH004", # Use specific rule codes when using noqa
"PLC0414", # Useless import alias. Import alias does not rename original package.
"SIM105", # Use contextlib.suppress({exception}) instead of try-except-pass
"SIM117", # Merge with-statements that use the same scope
"SIM118", # Use {key} in {dict} instead of {key} in {dict}.keys()
"SIM201", # Use {left} != {right} instead of not {left} == {right}
"SIM212", # Use {a} if {a} else {b} instead of {b} if not {a} else {a}
"SIM300", # Yoda conditions. Use 'age == 42' instead of '42 == age'.
"SIM401", # Use get from dict with default instead of an if block
"T20", # flake8-print
"TRY004", # Prefer TypeError exception for invalid type
"RUF006", # Store a reference to the return value of asyncio.create_task
"UP", # pyupgrade
"W", # pycodestyle
]

ignore = [
"D202", # No blank lines allowed after function docstring
"D203", # 1 blank line required before class docstring
"D213", # Multi-line docstring summary should start at the second line
"D404", # First word of the docstring should not be This
"D406", # Section name should end with a newline
"D407", # Section name underlining
"D411", # Missing blank line before section
"E501", # line too long
"E731", # do not assign a lambda expression, use a def
]

[flake8-pytest-style]
fixture-parentheses = false

[pyupgrade]
keep-runtime-typing = true

[mccabe]
max-complexity = 25
17 changes: 17 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Run Home Assistant on port 8123",
"type": "shell",
"command": "scripts/develop",
"problemMatcher": []
},
{
"label": "Lint (run pre-commit hooks)",
"type": "shell",
"command": "scripts/lint",
"problemMatcher": []
}
]
}
14 changes: 9 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,11 @@ RUN pip3 install -r /core/requirements.txt --use-pep517 && \
pip3 install -r /core/requirements_test.txt --use-pep517 && \
pip3 install -e /core/ --use-pep517

# Clone the Adaptive Lighting repository
RUN git clone https://github.com/basnijholt/adaptive-lighting.git /app
# Copy the Adaptive Lighting repository
COPY . /app/

# Setup symlinks in core
RUN ln -s /app/custom_components/adaptive_lighting /core/homeassistant/components/adaptive_lighting && \
ln -s /app/tests /core/tests/components/adaptive_lighting && \
RUN ln -s /app/tests /core/tests/components/adaptive_lighting && \
# For test_dependencies.py
ln -s /core /app/core

Expand All @@ -37,6 +36,11 @@ RUN pip3 install $(python3 /app/test_dependencies.py) --use-pep517

WORKDIR /core

# Make 'custom_components/adaptive_lighting' imports available to tests
ENV PYTHONPATH="${PYTHONPATH}:/app"
# Enable testing against HA clone (instead of pytest_homeassistant_custom_component)
ENV HA_CLONE=true

ENTRYPOINT ["python3", \
# Enable Python development mode
"-X", "dev", \
Expand All @@ -49,7 +53,7 @@ ENTRYPOINT ["python3", \
# Print the 10 slowest tests
"--durations=10", \
# Measure code coverage for the 'homeassistant' package
"--cov='homeassistant'", \
"--cov=custom_components.adaptive_lighting", \
# Generate an XML report of the code coverage
"--cov-report=xml", \
# Generate an HTML report of the code coverage
Expand Down
19 changes: 19 additions & 0 deletions config/configuration.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# https://www.home-assistant.io/integrations/default_config/
default_config:

# https://www.home-assistant.io/integrations/logger/
logger:
default: info
logs:
custom_components.adaptive_lighting: debug

light:
- platform: template
lights:
dummylight:
friendly_name: "Dummy Light"
turn_on:
turn_off:
set_level:
set_temperature:
supports_transition_template: "{{ true }}"
10 changes: 10 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
colorlog==6.7.0
pip>=21.0,<23.2
ruff==0.0.265
pre-commit

# Install HA and test dependencies (pytest, coverage)
# To pin the dev container to a specific HA version, set this dependency
# to the adequate version (add `==<version>`) and rebuild the dev container.
# See https://github.com/MatthewFlamm/pytest-homeassistant-custom-component/releases for version mappings.
pytest-homeassistant-custom-component
20 changes: 20 additions & 0 deletions scripts/develop
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env bash

set -e

cd "$(dirname "$0")/.."

# Create config dir if not present
if [[ ! -d "${PWD}/config" ]]; then
mkdir -p "${PWD}/config"
hass --config "${PWD}/config" --script ensure_config
fi

# Set the path to custom_components
## This let's us have the structure we want <root>/custom_components/adaptive_lighting
## while at the same time have Home Assistant configuration inside <root>/config
## without resulting to symlinks.
export PYTHONPATH="${PYTHONPATH}:${PWD}/custom_components"

# Start Home Assistant
hass --config "${PWD}/config" --debug
7 changes: 7 additions & 0 deletions scripts/lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env bash

set -e

cd "$(dirname "$0")/.."

pre-commit run --all-files
8 changes: 8 additions & 0 deletions scripts/setup
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env bash

set -e

cd "$(dirname "$0")/.."

python3 -m pip install --requirement requirements.txt
pre-commit install-hooks
4 changes: 4 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ max-complexity = 18
select = B,C,E,F,W,T4,B9
per-file-ignores =
code_example.py: E402, E501

[tool:pytest]
testpaths = tests
asyncio_mode = auto
2 changes: 1 addition & 1 deletion tests/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Developer notes for the tests directory

To run the tests, check out the [CI configuration](../.github/workflows/pytest.yml) to see how they are executed in the CI pipeline.
Alternatively, you can use the provided Docker image to run the tests locally.
Alternatively, you can use the provided Docker image to run the tests locally or run them with VS Code directly in the dev container.

To run the tests using the Docker image, navigate to the `adaptive-lighting` repo folder and execute the following command:

Expand Down
19 changes: 19 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""Fixtures for testing."""
import os
import sys

import pytest

# Tests in the dev enviromentment use the pytest_homeassistant_custom_component instead of
# a cloned HA core repo for a simple and clean structure. To still test against a HA core
# clone (e.g. the dev branch for which no pytest_homeassistant_custom_component exists
# because HA does not publish dev snapshot packages), set the HA_CLONE env variable.
if "HA_CLONE" in os.environ:
# Rewire the testing package to the cloned test modules. See the test `Dockerfile`
# for setup details.
sys.modules["pytest_homeassistant_custom_component"] = __import__("tests")


@pytest.fixture(autouse=True)
def auto_enable_custom_integrations(enable_custom_integrations):
yield
10 changes: 5 additions & 5 deletions tests/test_config_flow.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
"""Test Adaptive Lighting config flow."""
from homeassistant import data_entry_flow
from homeassistant.components.adaptive_lighting.const import (
from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import CONF_NAME
from pytest_homeassistant_custom_component.common import MockConfigEntry

from custom_components.adaptive_lighting.const import (
CONF_SUNRISE_TIME,
CONF_SUNSET_TIME,
DEFAULT_NAME,
DOMAIN,
NONE_STR,
VALIDATION_TUPLES,
)
from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import CONF_NAME

from tests.common import MockConfigEntry

DEFAULT_DATA = {key: default for key, default, _ in VALIDATION_TUPLES}

Expand Down
9 changes: 3 additions & 6 deletions tests/test_init.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
"""Tests for Adaptive Lighting integration."""
from homeassistant.components import adaptive_lighting
from homeassistant.components.adaptive_lighting.const import (
DEFAULT_NAME,
UNDO_UPDATE_LISTENER,
)
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import CONF_NAME
from homeassistant.setup import async_setup_component
from pytest_homeassistant_custom_component.common import MockConfigEntry

from tests.common import MockConfigEntry
from custom_components import adaptive_lighting
from custom_components.adaptive_lighting.const import DEFAULT_NAME, UNDO_UPDATE_LISTENER


async def test_setup_with_config(hass):
Expand Down
Loading

0 comments on commit 6283158

Please sign in to comment.