|
1 | 1 | # (C) Datadog, Inc. 2024-present
|
2 | 2 | # All rights reserved
|
3 | 3 | # Licensed under a 3-clause BSD style license (see LICENSE)
|
4 |
| -from contextlib import nullcontext |
5 | 4 |
|
6 |
| -import mock |
| 5 | +import json |
| 6 | +from collections.abc import Callable, Generator, Mapping |
| 7 | +from typing import Any |
| 8 | +from unittest.mock import MagicMock |
| 9 | + |
7 | 10 | import pytest
|
| 11 | +from click.testing import Result |
| 12 | +from pytest_mock import MockerFixture, MockType |
8 | 13 |
|
| 14 | +from ddev.e2e.config import EnvData, EnvDataStorage |
| 15 | +from ddev.utils.fs import Path |
9 | 16 | from tests.helpers.mocks import MockPopen
|
| 17 | +from tests.helpers.runner import CliRunner |
10 | 18 |
|
11 | 19 |
|
12 |
| -class MockEnvVars: |
13 |
| - def __init__(self, env_vars=None): |
14 |
| - assert env_vars['DDEV_REPO'] == 'core' |
| 20 | +def setup( |
| 21 | + mocker: MockerFixture, |
| 22 | + write_result_file: Callable[[Mapping[str, Any]], None], |
| 23 | + hatch_json_output: Mapping[str, Any] | str | None = None, |
| 24 | +): |
| 25 | + mocker.patch('subprocess.run', side_effect=write_result_file({'metadata': {}, 'config': {}})) |
15 | 26 |
|
16 |
| - def __enter__(*_args, **_kwargs): |
17 |
| - pass |
| 27 | + if hatch_json_output is not None: |
| 28 | + if isinstance(hatch_json_output, str): |
| 29 | + hatch_output = hatch_json_output.encode() |
| 30 | + elif isinstance(hatch_json_output, dict): |
| 31 | + hatch_output = json.dumps(hatch_json_output).encode() |
| 32 | + else: |
| 33 | + pytest.fail('Invalid hatch_json_output type') |
18 | 34 |
|
19 |
| - def __exit__(*_args, **_kwargs): |
20 |
| - pass |
| 35 | + mocker.patch('subprocess.Popen', return_value=MockPopen(returncode=0, stdout=hatch_output)) |
21 | 36 |
|
22 | 37 |
|
23 |
| -def test_env_vars_repo(ddev, helpers, data_dir, write_result_file, mocker): |
24 |
| - mocker.patch('subprocess.run', side_effect=write_result_file({'metadata': {}, 'config': {}})) |
25 |
| - mocker.patch('subprocess.Popen', return_value=MockPopen(returncode=0)) |
26 |
| - with mock.patch('ddev.utils.structures.EnvVars', side_effect=MockEnvVars): |
27 |
| - result = ddev('env', 'test', 'postgres', 'py3.12') |
28 |
| - assert result.exit_code == 0, result.output |
29 |
| - # Ensure test was not skipped |
30 |
| - assert "does not have E2E tests to run" not in result.output |
| 38 | +@pytest.fixture() |
| 39 | +def mock_commands(mocker: MockerFixture) -> Generator[tuple[MockType, MockType, MockType]]: |
| 40 | + start_mock = mocker.patch( |
| 41 | + 'ddev.cli.env.start.start', |
| 42 | + return_value=Result( |
| 43 | + return_value=0, |
| 44 | + runner=MagicMock(), |
| 45 | + stdout_bytes=b'', |
| 46 | + stderr_bytes=b'', |
| 47 | + exit_code=0, |
| 48 | + exception=None, |
| 49 | + ), |
| 50 | + ) |
| 51 | + stop_mock = mocker.patch( |
| 52 | + 'ddev.cli.env.stop.stop', |
| 53 | + return_value=Result( |
| 54 | + return_value=0, |
| 55 | + runner=MagicMock(), |
| 56 | + stdout_bytes=b'', |
| 57 | + stderr_bytes=b'', |
| 58 | + exit_code=0, |
| 59 | + exception=None, |
| 60 | + ), |
| 61 | + ) |
| 62 | + test_mock = mocker.patch( |
| 63 | + 'ddev.cli.test.test', |
| 64 | + return_value=Result( |
| 65 | + return_value=0, |
| 66 | + runner=MagicMock(), |
| 67 | + stdout_bytes=b'', |
| 68 | + stderr_bytes=b'', |
| 69 | + exit_code=0, |
| 70 | + exception=None, |
| 71 | + ), |
| 72 | + ) |
| 73 | + yield start_mock, stop_mock, test_mock |
| 74 | + |
| 75 | + |
| 76 | +def assert_commands_run(mock_commands: tuple[MockType, MockType, MockType], call_count: int = 1): |
| 77 | + assert mock_commands[0].call_count == call_count |
| 78 | + assert mock_commands[1].call_count == call_count |
| 79 | + assert mock_commands[2].call_count == call_count |
31 | 80 |
|
32 | 81 |
|
33 | 82 | @pytest.mark.parametrize(
|
34 |
| - 'target, expectation', |
| 83 | + 'e2e_env, predicate', |
35 | 84 | [
|
36 |
| - ('datadog_checks_dev', nullcontext()), |
37 |
| - ('datadog_checks_base', nullcontext()), |
38 |
| - # This will raise an OSError because the package is not a valid integration |
39 |
| - ('datadog_checks_tests_helper', pytest.raises(OSError)), |
40 |
| - ('ddev', nullcontext()), |
| 85 | + (False, lambda result: "disabled by e2e-env option" in result.output), |
| 86 | + (True, lambda result: "disabled by e2e-env option" not in result.output), |
41 | 87 | ],
|
42 |
| - ids=['datadog_checks_dev', 'datadog_checks_base', 'datadog_checks_tests_helper', 'ddev'], |
| 88 | + ids=['e2e-env-false', 'e2e-env-true'], |
43 | 89 | )
|
44 |
| -@pytest.mark.parametrize('env', ['py3.12', 'all', ''], ids=['py3.12', 'all', 'no-env']) |
45 |
| -def test_env_test_not_e2e_testable(ddev, target: str, env: str, expectation): |
46 |
| - with expectation: |
47 |
| - result = ddev('env', 'test', target, env) |
| 90 | +def test_env_vars_repo( |
| 91 | + ddev: CliRunner, |
| 92 | + data_dir: Path, |
| 93 | + write_result_file: Callable[[Mapping[str, Any]], None], |
| 94 | + mocker: MockerFixture, |
| 95 | + e2e_env: bool, |
| 96 | + predicate: Callable[[Result], bool], |
| 97 | + mock_commands: tuple[MockType, MockType, MockType], |
| 98 | +): |
| 99 | + setup(mocker, write_result_file, hatch_json_output={'py3.12': {'e2e-env': e2e_env}}) |
| 100 | + mocker.patch.object(EnvData, 'read_metadata', return_value={}) |
| 101 | + |
| 102 | + result = ddev('env', 'test', 'postgres', 'py3.12') |
| 103 | + assert result.exit_code == 0, result.output |
| 104 | + # Ensure test was not skipped |
| 105 | + assert predicate(result) |
| 106 | + assert_commands_run(mock_commands, 1 if e2e_env else 0) |
| 107 | + |
| 108 | + |
| 109 | +@pytest.mark.parametrize('environment, command_call_count', [('active', 0), ('all', 2), ('py3.12', 1)]) |
| 110 | +def test_environment_runs_for_enabled_environments( |
| 111 | + ddev: CliRunner, |
| 112 | + data_dir: Path, |
| 113 | + write_result_file: Callable[[Mapping[str, Any]], None], |
| 114 | + mocker: MockerFixture, |
| 115 | + environment: str, |
| 116 | + mock_commands: tuple[MockType, MockType, MockType], |
| 117 | + command_call_count: int, |
| 118 | +): |
| 119 | + setup( |
| 120 | + mocker, |
| 121 | + write_result_file, |
| 122 | + hatch_json_output={'py3.12': {'e2e-env': True}, 'py3.13': {'e2e-env': False}, 'py3.13-v1': {'e2e-env': True}}, |
| 123 | + ) |
| 124 | + with mocker.patch.object(EnvData, 'read_metadata', return_value={}): |
| 125 | + result = ddev('env', 'test', 'postgres', environment) |
| 126 | + assert result.exit_code == 0, result.output |
| 127 | + assert_commands_run(mock_commands, command_call_count) |
| 128 | + |
| 129 | + |
| 130 | +def test_command_errors_out_when_cannot_parse_json_output_from_hatch( |
| 131 | + ddev: CliRunner, |
| 132 | + data_dir: Path, |
| 133 | + write_result_file: Callable[[Mapping[str, Any]], None], |
| 134 | + mocker: MockerFixture, |
| 135 | +): |
| 136 | + setup(mocker, write_result_file, hatch_json_output='invalid json') |
| 137 | + result = ddev('env', 'test', 'postgres', 'py3.12') |
| 138 | + assert result.exit_code == 1, result.output |
| 139 | + |
| 140 | + |
| 141 | +def test_runningin_ci_triggers_all_environments_when_not_supplied( |
| 142 | + ddev: CliRunner, |
| 143 | + data_dir: Path, |
| 144 | + write_result_file: Callable[[Mapping[str, Any]], None], |
| 145 | + mocker: MockerFixture, |
| 146 | + mock_commands: tuple[MockType, MockType, MockType], |
| 147 | +): |
| 148 | + setup(mocker, write_result_file, hatch_json_output={'py3.12': {'e2e-env': True}, 'py3.13': {'e2e-env': True}}) |
| 149 | + mocker.patch('ddev.utils.ci.running_in_ci', return_value=True) |
| 150 | + |
| 151 | + with mocker.patch.object(EnvData, 'read_metadata', return_value={}): |
| 152 | + result = ddev('env', 'test', 'postgres') |
| 153 | + assert result.exit_code == 0, result.output |
| 154 | + assert_commands_run(mock_commands, 2) |
| 155 | + |
| 156 | + |
| 157 | +def test_run_only_active_environments_when_not_running_in_ci_and_active_environments_exist( |
| 158 | + ddev: CliRunner, |
| 159 | + data_dir: Path, |
| 160 | + write_result_file: Callable[[Mapping[str, Any]], None], |
| 161 | + mocker: MockerFixture, |
| 162 | + mock_commands: tuple[MockType, MockType, MockType], |
| 163 | +): |
| 164 | + setup(mocker, write_result_file, hatch_json_output={'py3.12': {'e2e-env': True}, 'py3.13': {'e2e-env': True}}) |
| 165 | + mocker.patch('ddev.utils.ci.running_in_ci', return_value=False) |
| 166 | + |
| 167 | + with ( |
| 168 | + mocker.patch.object(EnvData, 'read_metadata', return_value={}), |
| 169 | + mocker.patch.object(EnvDataStorage, 'get_environments', return_value=['py3.12']), |
| 170 | + ): |
| 171 | + result = ddev('env', 'test', 'postgres') |
48 | 172 | assert result.exit_code == 0, result.output
|
49 |
| - assert "does not have E2E tests to run" in result.output |
| 173 | + assert_commands_run(mock_commands, 1) |
0 commit comments