dc6079821b
CI Github Actions / Run Tests (push) Waiting to run
Automatic PR Merger / mergepal (push) Waiting to run
Semgrep / Semgrep Workflow Security Scan (push) Waiting to run
Docs Tests / Check for file changes (push) Has been cancelled
Docs Tests / Test Documentation (push) Has been cancelled
Docs Tests / Documentation Linting Checks (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-policies) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-policies) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-policies) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-policies) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-policies) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-policies) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (ubuntu-24.04, 3.10) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (ubuntu-24.04, 3.8) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (ubuntu-24.04, 3.9) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (windows-2022, 3.10) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (windows-2022, 3.8) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (windows-2022, 3.9) (push) Has been cancelled
Continuous Integration / Check for file changes (push) Has been cancelled
Continuous Integration / Wait for docs tests (push) Has been cancelled
Continuous Integration / Code Quality (push) Has been cancelled
Continuous Integration / Check for changelog (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Upload coverage reports to codeclimate (push) Has been cancelled
Continuous Integration / Run Non-Sequential Integration Tests (push) Has been cancelled
Continuous Integration / Run Broker Integration Tests (push) Has been cancelled
Continuous Integration / Run Sequential Integration Tests (push) Has been cancelled
Continuous Integration / Build Docker base images and setup environment (push) Has been cancelled
Continuous Integration / Build Docker (default) (push) Has been cancelled
Continuous Integration / Build Docker (full) (push) Has been cancelled
Continuous Integration / Build Docker (mitie-en) (push) Has been cancelled
Continuous Integration / Build Docker (spacy-de) (push) Has been cancelled
Continuous Integration / Build Docker (spacy-en) (push) Has been cancelled
Continuous Integration / Build Docker (spacy-it) (push) Has been cancelled
Continuous Integration / Deploy to PyPI (push) Has been cancelled
Continuous Integration / Notify Slack & Publish Release Notes (push) Has been cancelled
Publish Documentation / Evaluate release tag (push) Has been cancelled
Publish Documentation / Prebuild Docs (push) Has been cancelled
Publish Documentation / Preview Docs (push) Has been cancelled
Publish Documentation / Check for file changes (push) Has been cancelled
Publish Documentation / Publish Docs (push) Has been cancelled
932 lines
27 KiB
Python
932 lines
27 KiB
Python
import asyncio
|
|
import contextlib
|
|
import copy
|
|
import os
|
|
import random
|
|
import re
|
|
import textwrap
|
|
|
|
import jwt
|
|
import pytest
|
|
import sys
|
|
import uuid
|
|
|
|
from pytest import TempdirFactory, MonkeyPatch, Function, TempPathFactory
|
|
from spacy import Language
|
|
from pytest import WarningsRecorder
|
|
|
|
from rasa.engine.caching import LocalTrainingCache
|
|
from rasa.engine.graph import ExecutionContext, GraphSchema
|
|
from rasa.engine.storage.local_model_storage import LocalModelStorage
|
|
from rasa.engine.storage.storage import ModelStorage
|
|
from sanic.request import Request
|
|
|
|
from typing import Generator, Iterator, Callable
|
|
|
|
from pathlib import Path
|
|
from sanic import Sanic
|
|
from typing import Text, List, Optional, Dict, Any
|
|
from unittest.mock import Mock
|
|
|
|
from rasa.shared.core.constants import (
|
|
ACTION_LISTEN_NAME,
|
|
ACTION_RESTART_NAME,
|
|
ACTION_SESSION_START_NAME,
|
|
)
|
|
from rasa.shared.core.trackers import DialogueStateTracker
|
|
from rasa.shared.nlu.constants import METADATA_MODEL_ID
|
|
import rasa.shared.utils.io
|
|
from rasa import server
|
|
from rasa.core.agent import Agent, load_agent
|
|
from rasa.core.brokers.broker import EventBroker
|
|
from rasa.core.channels import channel, RestInput
|
|
from rasa.nlu.tokenizers.whitespace_tokenizer import WhitespaceTokenizer
|
|
|
|
from rasa.nlu.utils.spacy_utils import SpacyNLP, SpacyModel
|
|
from rasa.shared.constants import ASSISTANT_ID_KEY, LATEST_TRAINING_DATA_FORMAT_VERSION
|
|
from rasa.shared.core.domain import SessionConfig, Domain
|
|
from rasa.shared.core.events import (
|
|
ActionExecuted,
|
|
Event,
|
|
Restarted,
|
|
SessionStarted,
|
|
UserUttered,
|
|
)
|
|
from rasa.core.exporter import Exporter
|
|
|
|
import rasa.core.run
|
|
from rasa.core.tracker_store import InMemoryTrackerStore, TrackerStore
|
|
from rasa.model_training import train, train_nlu
|
|
from rasa.shared.exceptions import RasaException
|
|
import rasa.utils.common
|
|
import rasa.utils.io
|
|
|
|
|
|
# we reuse a bit of pytest's own testing machinery, this should eventually come
|
|
# from a separately installable pytest-cli plugin.
|
|
pytest_plugins = ["pytester"]
|
|
|
|
|
|
# these tests are run separately
|
|
collect_ignore_glob = ["docs/*.py"]
|
|
|
|
# Defines how tests are parallelized in the CI
|
|
PATH_PYTEST_MARKER_MAPPINGS = {
|
|
"category_cli": [Path("tests", "cli").absolute()],
|
|
"category_core_featurizers": [Path("tests", "core", "featurizers").absolute()],
|
|
"category_policies": [
|
|
Path("tests", "core", "test_policies.py").absolute(),
|
|
Path("tests", "core", "policies").absolute(),
|
|
],
|
|
"category_nlu_featurizers": [
|
|
Path("tests", "nlu", "featurizers").absolute(),
|
|
Path("tests", "nlu", "utils").absolute(),
|
|
],
|
|
"category_nlu_predictors": [
|
|
Path("tests", "nlu", "classifiers").absolute(),
|
|
Path("tests", "nlu", "extractors").absolute(),
|
|
Path("tests", "nlu", "selectors").absolute(),
|
|
],
|
|
"category_full_model_training": [
|
|
Path("tests", "test_model_training.py").absolute(),
|
|
Path("tests", "nlu", "test_train.py").absolute(),
|
|
Path("tests", "core", "test_training.py").absolute(),
|
|
Path("tests", "core", "test_examples.py").absolute(),
|
|
],
|
|
"category_performance": [Path("tests", "test_memory_leak.py").absolute()],
|
|
}
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def nlu_as_json_path() -> Text:
|
|
return "data/examples/rasa/demo-rasa.json"
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def nlu_data_path() -> Text:
|
|
return "data/test_moodbot/data/nlu.yml"
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def config_path() -> Text:
|
|
return "rasa/engine/recipes/config_files/default_config.yml"
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def default_config(config_path: Text) -> Dict[Text, Any]:
|
|
return rasa.shared.utils.io.read_yaml_file(config_path)
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def domain_with_categorical_slot_path() -> Text:
|
|
return "data/test_domains/domain_with_categorical_slot.yml"
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def domain_with_mapping_path() -> Text:
|
|
return "data/test_domains/default_with_mapping.yml"
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def stories_path() -> Text:
|
|
return "data/test_yaml_stories/stories_defaultdomain.yml"
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def e2e_stories_path() -> Text:
|
|
return "data/test_yaml_stories/stories_e2e.yml"
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def simple_stories_path() -> Text:
|
|
return "data/test_yaml_stories/stories_simple.yml"
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def stack_config_path() -> Text:
|
|
return "data/test_config/stack_config.yml"
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def incorrect_nlu_data_path() -> Text:
|
|
return "data/test/incorrect_nlu_format.yml"
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def end_to_end_story_path() -> Text:
|
|
return "data/test_evaluations/test_end_to_end_story.yml"
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def e2e_story_file_unknown_entity_path() -> Text:
|
|
return "data/test_evaluations/test_story_unknown_entity.yml"
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def domain_path() -> Text:
|
|
return "data/test_domains/default_with_slots.yml"
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def simple_config_path(tmp_path_factory: TempPathFactory) -> Text:
|
|
project_path = tmp_path_factory.mktemp(uuid.uuid4().hex)
|
|
|
|
config = textwrap.dedent(
|
|
f"""
|
|
version: "{LATEST_TRAINING_DATA_FORMAT_VERSION}"
|
|
assistant_id: placeholder_default
|
|
pipeline:
|
|
- name: WhitespaceTokenizer
|
|
- name: KeywordIntentClassifier
|
|
- name: RegexEntityExtractor
|
|
policies:
|
|
- name: AugmentedMemoizationPolicy
|
|
max_history: 3
|
|
- name: RulePolicy
|
|
"""
|
|
)
|
|
config_path = project_path / "config.yml"
|
|
rasa.shared.utils.io.write_text_file(config, config_path)
|
|
|
|
return str(config_path)
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def story_file_trips_circuit_breaker_path() -> Text:
|
|
return "data/test_evaluations/test_stories_trip_circuit_breaker.yml"
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def e2e_story_file_trips_circuit_breaker_path() -> Text:
|
|
return "data/test_evaluations/test_end_to_end_trips_circuit_breaker.yml"
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def endpoints_path() -> Text:
|
|
return "data/test_endpoints/example_endpoints.yml"
|
|
|
|
|
|
# https://github.com/pytest-dev/pytest-asyncio/issues/68
|
|
# this event_loop is used by pytest-asyncio, and redefining it
|
|
# is currently the only way of changing the scope of this fixture
|
|
# update: implement fix to RuntimeError Event loop is closed issue described
|
|
# here: https://github.com/pytest-dev/pytest-asyncio/issues/371
|
|
@pytest.fixture(scope="session")
|
|
def event_loop(request: Request) -> Iterator[asyncio.AbstractEventLoop]:
|
|
loop = asyncio.get_event_loop_policy().new_event_loop()
|
|
loop._close = loop.close
|
|
loop.close = lambda: None
|
|
yield loop
|
|
loop._close()
|
|
|
|
|
|
# override loop fixture to prevent ScopeMismatch pytest error and
|
|
# align the result of the loop fixture with that of the event_loop fixture
|
|
@pytest.fixture(scope="session")
|
|
def loop(
|
|
event_loop: asyncio.AbstractEventLoop,
|
|
) -> Generator[asyncio.AbstractEventLoop, None, None]:
|
|
yield event_loop
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
async def trained_default_agent_model(
|
|
stories_path: Text,
|
|
domain_path: Text,
|
|
nlu_data_path: Text,
|
|
trained_async: Callable,
|
|
simple_config_path: Text,
|
|
) -> Text:
|
|
model_path = await trained_async(
|
|
domain_path, simple_config_path, [stories_path, nlu_data_path]
|
|
)
|
|
|
|
return model_path
|
|
|
|
|
|
@pytest.fixture()
|
|
def empty_agent() -> Agent:
|
|
agent = Agent(domain=Domain.load("data/test_domains/default_with_slots.yml"))
|
|
return agent
|
|
|
|
|
|
def reset_conversation_state(agent: Agent) -> Agent:
|
|
# Clean tracker store after each test so tests don't affect each other
|
|
agent.tracker_store = InMemoryTrackerStore(agent.domain)
|
|
agent.domain.session_config = SessionConfig.default()
|
|
agent.load_model(agent.processor.model_path)
|
|
return agent
|
|
|
|
|
|
@pytest.fixture
|
|
def default_agent(trained_default_agent_model: Text) -> Agent:
|
|
return Agent.load(trained_default_agent_model)
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
async def trained_moodbot_path(trained_async: Callable) -> Text:
|
|
return await trained_async(
|
|
domain="data/test_moodbot/domain.yml",
|
|
config="data/test_moodbot/config.yml",
|
|
training_files="data/test_moodbot/data/",
|
|
)
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
async def trained_moodbot_core_path(trained_async: Callable) -> Text:
|
|
return await trained_async(
|
|
domain="data/test_moodbot/domain.yml",
|
|
config="data/test_moodbot/config.yml",
|
|
training_files="data/test_moodbot/data/stories.yml",
|
|
)
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
async def trained_moodbot_nlu_path(trained_async: Callable) -> Text:
|
|
return await trained_async(
|
|
domain="data/test_moodbot/domain.yml",
|
|
config="data/test_moodbot/config.yml",
|
|
training_files="data/test_moodbot/data/nlu.yml",
|
|
)
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
async def trained_unexpected_intent_policy_path(trained_async: Callable) -> Text:
|
|
return await trained_async(
|
|
domain="data/test_moodbot/domain.yml",
|
|
config="data/test_moodbot/unexpected_intent_policy_config.yml",
|
|
training_files="data/test_moodbot/data/",
|
|
)
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def trained_nlu_moodbot_path(trained_nlu: Callable) -> Text:
|
|
return trained_nlu(
|
|
domain="data/test_moodbot/domain.yml",
|
|
config="data/test_moodbot/config.yml",
|
|
nlu_data="data/test_moodbot/data/nlu.yml",
|
|
)
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
async def trained_spacybot_path(trained_async: Callable) -> Text:
|
|
return await trained_async(
|
|
domain="data/test_spacybot/domain.yml",
|
|
config="data/test_spacybot/config.yml",
|
|
training_files="data/test_spacybot/data/",
|
|
)
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
async def stack_agent(trained_rasa_model: Text) -> Agent:
|
|
return await load_agent(model_path=trained_rasa_model)
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
async def core_agent(trained_core_model: Text) -> Agent:
|
|
return await load_agent(model_path=trained_core_model)
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
async def nlu_agent(trained_nlu_model: Text) -> Agent:
|
|
return await load_agent(model_path=trained_nlu_model)
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
async def unexpected_intent_policy_agent(
|
|
trained_unexpected_intent_policy_path: Text,
|
|
) -> Agent:
|
|
return await load_agent(model_path=trained_unexpected_intent_policy_path)
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
async def mood_agent(trained_moodbot_path: Text) -> Agent:
|
|
return await load_agent(model_path=trained_moodbot_path)
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def _domain(domain_path: Text) -> Domain:
|
|
return Domain.load(domain_path)
|
|
|
|
|
|
@pytest.fixture()
|
|
def domain(_domain: Domain) -> Domain:
|
|
return copy.deepcopy(_domain)
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def trained_async(tmp_path_factory: TempPathFactory) -> Callable:
|
|
async def _train(
|
|
*args: Any,
|
|
output_path: Optional[Text] = None,
|
|
cache_dir: Optional[Path] = None,
|
|
**kwargs: Any,
|
|
) -> Optional[Text]:
|
|
if not cache_dir:
|
|
cache_dir = tmp_path_factory.mktemp("cache")
|
|
|
|
if output_path is None:
|
|
output_path = str(tmp_path_factory.mktemp("models"))
|
|
|
|
with enable_cache(cache_dir):
|
|
result = train(*args, output=output_path, **kwargs)
|
|
|
|
return result.model
|
|
|
|
return _train
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def trained_nlu(tmp_path_factory: TempPathFactory) -> Callable:
|
|
def _train_nlu(
|
|
*args: Any, output_path: Optional[Text] = None, **kwargs: Any
|
|
) -> Optional[Text]:
|
|
if output_path is None:
|
|
output_path = str(tmp_path_factory.mktemp("models"))
|
|
|
|
return train_nlu(*args, output=output_path, **kwargs)
|
|
|
|
return _train_nlu
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
async def trained_rasa_model(
|
|
trained_async: Callable,
|
|
domain_path: Text,
|
|
nlu_data_path: Text,
|
|
stories_path: Text,
|
|
stack_config_path: Text,
|
|
) -> Text:
|
|
trained_stack_model_path = await trained_async(
|
|
domain=domain_path,
|
|
config=stack_config_path,
|
|
training_files=[nlu_data_path, stories_path],
|
|
)
|
|
|
|
return trained_stack_model_path
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
async def trained_core_model(
|
|
trained_async: Callable,
|
|
domain_path: Text,
|
|
stack_config_path: Text,
|
|
stories_path: Text,
|
|
) -> Text:
|
|
trained_core_model_path = await trained_async(
|
|
domain=domain_path, config=stack_config_path, training_files=[stories_path]
|
|
)
|
|
|
|
return trained_core_model_path
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
async def trained_nlu_model(
|
|
trained_async: Callable,
|
|
domain_path: Text,
|
|
nlu_data_path: Text,
|
|
stack_config_path: Text,
|
|
) -> Text:
|
|
trained_nlu_model_path = await trained_async(
|
|
domain=domain_path, config=stack_config_path, training_files=[nlu_data_path]
|
|
)
|
|
|
|
return trained_nlu_model_path
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def _trained_e2e_model_cache(tmp_path_factory: TempPathFactory) -> Path:
|
|
return tmp_path_factory.mktemp("cache")
|
|
|
|
|
|
@pytest.fixture()
|
|
def trained_e2e_model_cache(
|
|
_trained_e2e_model_cache: Path,
|
|
tmp_path_factory: TempPathFactory,
|
|
monkeypatch: MonkeyPatch,
|
|
) -> Path:
|
|
copied_cache = tmp_path_factory.mktemp("copy")
|
|
rasa.utils.common.copy_directory(_trained_e2e_model_cache, copied_cache)
|
|
|
|
with enable_cache(copied_cache):
|
|
yield copied_cache
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
async def trained_e2e_model(
|
|
trained_async: Callable,
|
|
moodbot_domain_path: Text,
|
|
e2e_bot_config_file: Path,
|
|
nlu_data_path: Text,
|
|
e2e_stories_path: Text,
|
|
_trained_e2e_model_cache: Path,
|
|
) -> Text:
|
|
return await trained_async(
|
|
domain=moodbot_domain_path,
|
|
config=str(e2e_bot_config_file),
|
|
training_files=[nlu_data_path, e2e_stories_path],
|
|
cache_dir=_trained_e2e_model_cache,
|
|
)
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def moodbot_domain_path() -> Path:
|
|
return Path("data", "test_moodbot", "domain.yml")
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def moodbot_domain(moodbot_domain_path: Path) -> Domain:
|
|
return Domain.load(moodbot_domain_path)
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def moodbot_nlu_data_path() -> Path:
|
|
return Path(os.getcwd()) / "data" / "test_moodbot" / "data" / "nlu.yml"
|
|
|
|
|
|
@pytest.fixture
|
|
def rasa_server(stack_agent: Agent) -> Sanic:
|
|
app = server.create_app(agent=stack_agent)
|
|
channel.register([RestInput()], app, "/webhooks/")
|
|
return app
|
|
|
|
|
|
@pytest.fixture
|
|
def rasa_non_trained_server(empty_agent: Agent) -> Sanic:
|
|
app = server.create_app(agent=empty_agent)
|
|
channel.register([RestInput()], app, "/webhooks/")
|
|
return app
|
|
|
|
|
|
@pytest.fixture
|
|
def rasa_core_server(core_agent: Agent) -> Sanic:
|
|
app = server.create_app(agent=core_agent)
|
|
channel.register([RestInput()], app, "/webhooks/")
|
|
return app
|
|
|
|
|
|
@pytest.fixture
|
|
def rasa_nlu_server(nlu_agent: Agent) -> Sanic:
|
|
app = server.create_app(agent=nlu_agent)
|
|
channel.register([RestInput()], app, "/webhooks/")
|
|
return app
|
|
|
|
|
|
@pytest.fixture
|
|
def rasa_server_secured(default_agent: Agent) -> Sanic:
|
|
app = server.create_app(agent=default_agent, auth_token="rasa", jwt_secret="core")
|
|
channel.register([RestInput()], app, "/webhooks/")
|
|
return app
|
|
|
|
|
|
@pytest.fixture
|
|
def test_public_key() -> Text:
|
|
test_public_key = """-----BEGIN PUBLIC KEY-----
|
|
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC34ht9inqGq79HecpyOAnu2Cgv
|
|
jvgcpFifpFLPmCNdiomAgE48tfUAXJRoOGlVtrqc8KgQWjTFLjqDjUh1sBFF69Fl
|
|
wQGt7pgH10ZbERWpMTAbpjI9EoH74gDcmZ6Fy1VgQPbAwty3liw5Q5zqZLj7JhuX
|
|
Sa0EqvZQP+Hnayab7QIDAQAB
|
|
-----END PUBLIC KEY-----"""
|
|
|
|
return test_public_key
|
|
|
|
|
|
@pytest.fixture
|
|
def test_private_key() -> Text:
|
|
test_private_key = """-----BEGIN RSA PRIVATE KEY-----
|
|
MIICXQIBAAKBgQC34ht9inqGq79HecpyOAnu2CgvjvgcpFifpFLPmCNdiomAgE48
|
|
tfUAXJRoOGlVtrqc8KgQWjTFLjqDjUh1sBFF69FlwQGt7pgH10ZbERWpMTAbpjI9
|
|
EoH74gDcmZ6Fy1VgQPbAwty3liw5Q5zqZLj7JhuXSa0EqvZQP+Hnayab7QIDAQAB
|
|
AoGBAIfUE25mjh9QWljX0/0O+/db4ENRHmE53OT/otQJk4YTQYKURDaASdvchxt9
|
|
IAHamno3Ik4B9Bz7CuoFwNJ+HiMBf32KwJ75n/NZL17lBKst71z3r0gYCz6jcJxv
|
|
brbNs8qsLFyRMQz6NvS4d4GnXpGhc54IoJqtr/vR+Q87UwtZAkEA3AG78E7Fd5zT
|
|
sU/BO9E0VisQOysGcwPd9+rQPSyF8ncvaiMJ7STNvVsgrtJuw4DJq2RsMSJ77QgS
|
|
Ku6BJxB58wJBANX3dOEiNEZLJR+4LdNYRoR4gx2LcJW5PthwLi8ZOHBZeh9q3f2i
|
|
r5X5iPJ5kBRqajtYm634f/j8P4fxSdWzKp8CQQCNimQR92udR3z+HxRvWml0YmIf
|
|
3s9YYY2FeUEdii5mznznqMEzGzFt+Fmvf1yZVJrqNEJS3h+iYEXn7ueSbUw3AkBm
|
|
xSK4d+tP0AwWvioUlxPX0OJ5MF51K7LJ1qf4K072d6O2r2fMyXU4vdBPVqAjjjFU
|
|
K+0qlG8zMkV5kCV8pT/VAkA8bM5KRa73JY0bfGX4i8UZMFHzIq2KGjHlRES4vd+L
|
|
h18+hpcBAAyUR/jDT8nnG5YaYFz8rf2DnOy+elmmaYVm
|
|
-----END RSA PRIVATE KEY-----"""
|
|
|
|
return test_private_key
|
|
|
|
|
|
@pytest.fixture
|
|
def asymmetric_jwt_method() -> Text:
|
|
return "RS256"
|
|
|
|
|
|
@pytest.fixture
|
|
def rasa_server_secured_asymmetric(
|
|
default_agent: Agent,
|
|
test_public_key: Text,
|
|
test_private_key: Text,
|
|
asymmetric_jwt_method: Text,
|
|
) -> Sanic:
|
|
app = server.create_app(
|
|
agent=default_agent,
|
|
auth_token="rasa",
|
|
jwt_secret=test_public_key,
|
|
jwt_private_key=test_private_key,
|
|
jwt_method=asymmetric_jwt_method,
|
|
)
|
|
channel.register([RestInput()], app, "/webhooks/")
|
|
return app
|
|
|
|
|
|
@pytest.fixture
|
|
def encoded_jwt(test_private_key: Text, asymmetric_jwt_method: Text) -> Text:
|
|
payload = {"user": {"username": "myuser", "role": "admin"}}
|
|
encoded_jwt = jwt.encode(
|
|
payload=payload,
|
|
key=test_private_key,
|
|
algorithm=asymmetric_jwt_method,
|
|
)
|
|
return encoded_jwt
|
|
|
|
|
|
@pytest.fixture
|
|
def rasa_non_trained_server_secured(empty_agent: Agent) -> Sanic:
|
|
app = server.create_app(agent=empty_agent, auth_token="rasa", jwt_secret="core")
|
|
channel.register([RestInput()], app, "/webhooks/")
|
|
return app
|
|
|
|
|
|
@pytest.fixture
|
|
def rasa_server_without_api() -> Sanic:
|
|
app = rasa.core.run._create_app_without_api()
|
|
channel.register([RestInput()], app, "/webhooks/")
|
|
return app
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def project() -> Text:
|
|
import tempfile
|
|
from rasa.cli.scaffold import create_initial_project
|
|
|
|
directory = tempfile.mkdtemp()
|
|
create_initial_project(directory)
|
|
|
|
return directory
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def spacy_nlp_component() -> SpacyNLP:
|
|
return SpacyNLP.create({"model": "en_core_web_md"}, Mock(), Mock(), Mock())
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def spacy_case_sensitive_nlp_component() -> SpacyNLP:
|
|
return SpacyNLP.create(
|
|
{"model": "en_core_web_md", "case_sensitive": True}, Mock(), Mock(), Mock()
|
|
)
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def spacy_model(spacy_nlp_component: SpacyNLP) -> SpacyModel:
|
|
return spacy_nlp_component.provide()
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def spacy_nlp(spacy_model: SpacyModel) -> Language:
|
|
return spacy_model.model
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
async def response_selector_test_stories() -> Path:
|
|
return Path("data/test_response_selector_bot/tests/test_stories.yml")
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
async def trained_response_selector_bot(trained_async: Callable) -> Path:
|
|
zipped_model = await trained_async(
|
|
domain="data/test_response_selector_bot/domain.yml",
|
|
config="data/test_response_selector_bot/config.yml",
|
|
training_files=[
|
|
"data/test_response_selector_bot/data/rules.yml",
|
|
"data/test_response_selector_bot/data/nlu.yml",
|
|
],
|
|
)
|
|
|
|
if not zipped_model:
|
|
raise RasaException("Model training for responseselectorbot failed.")
|
|
|
|
return Path(zipped_model)
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def e2e_bot_domain_file() -> Path:
|
|
return Path("data/test_e2ebot/domain.yml")
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def e2e_bot_config_file() -> Path:
|
|
return Path("data/test_e2ebot/config.yml")
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def e2e_bot_training_files() -> List[Path]:
|
|
return [
|
|
Path("data/test_e2ebot/data/stories.yml"),
|
|
Path("data/test_e2ebot/data/nlu.yml"),
|
|
]
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def e2e_bot_test_stories_with_unknown_bot_utterances() -> Path:
|
|
return Path("data/test_e2ebot/tests/test_stories_with_unknown_bot_utterances.yml")
|
|
|
|
|
|
# FIXME: This fixture is very slow, do not use it without fixing that first
|
|
@pytest.fixture(scope="session")
|
|
async def e2e_bot(
|
|
trained_async: Callable,
|
|
e2e_bot_domain_file: Path,
|
|
e2e_bot_config_file: Path,
|
|
e2e_bot_training_files: List[Path],
|
|
) -> Path:
|
|
zipped_model = await trained_async(
|
|
domain=e2e_bot_domain_file,
|
|
config=e2e_bot_config_file,
|
|
training_files=e2e_bot_training_files,
|
|
)
|
|
|
|
if not zipped_model:
|
|
raise RasaException("Model training for e2ebot failed.")
|
|
|
|
return Path(zipped_model)
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
async def response_selector_agent(trained_response_selector_bot: Path) -> Agent:
|
|
return await load_agent(str(trained_response_selector_bot))
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
async def e2e_bot_agent(e2e_bot: Path) -> Agent:
|
|
return await load_agent(str(e2e_bot))
|
|
|
|
|
|
def write_endpoint_config_to_yaml(
|
|
path: Path, data: Dict[Text, Any], endpoints_filename: Text = "endpoints.yml"
|
|
) -> Path:
|
|
endpoints_path = path / endpoints_filename
|
|
|
|
# write endpoints config to file
|
|
rasa.shared.utils.io.write_yaml(data, endpoints_path)
|
|
return endpoints_path
|
|
|
|
|
|
def random_user_uttered_event(timestamp: Optional[float] = None) -> UserUttered:
|
|
return UserUttered(
|
|
uuid.uuid4().hex,
|
|
timestamp=timestamp if timestamp is not None else random.random(),
|
|
)
|
|
|
|
|
|
def pytest_runtest_setup(item: Function) -> None:
|
|
if (
|
|
"skip_on_windows" in [mark.name for mark in item.iter_markers()]
|
|
and sys.platform == "win32"
|
|
):
|
|
pytest.skip("cannot run on Windows")
|
|
if "skip_on_ci" in [mark.name for mark in item.iter_markers()] and os.environ.get(
|
|
"CI"
|
|
) in ["true", "True", "yes", "t", "1"]:
|
|
pytest.skip("cannot run on CI")
|
|
|
|
|
|
class MockExporter(Exporter):
|
|
"""Mocked `Exporter` class."""
|
|
|
|
def __init__(
|
|
self,
|
|
tracker_store: TrackerStore = Mock(),
|
|
event_broker: EventBroker = Mock(),
|
|
endpoints_path: Text = "",
|
|
) -> None:
|
|
super().__init__(tracker_store, event_broker, endpoints_path)
|
|
|
|
|
|
class AsyncMock(Mock):
|
|
"""Helper class to mock async functions and methods."""
|
|
|
|
async def __call__(self, *args: Any, **kwargs: Any) -> Any:
|
|
return super().__call__(*args, **kwargs)
|
|
|
|
|
|
def _get_marker_for_ci_matrix(item: Function) -> Text:
|
|
"""Returns pytest marker which is used to parallelize the tests in GitHub actions.
|
|
|
|
Args:
|
|
item: The test case.
|
|
|
|
Returns:
|
|
A marker for this test based on which directory / python module the test is in.
|
|
"""
|
|
test_path = Path(item.fspath).absolute()
|
|
|
|
matching_markers = [
|
|
marker
|
|
for marker, paths_for_marker in PATH_PYTEST_MARKER_MAPPINGS.items()
|
|
if any(
|
|
path == test_path or path in test_path.parents for path in paths_for_marker
|
|
)
|
|
]
|
|
|
|
if not matching_markers:
|
|
return "category_other_unit_tests"
|
|
if len(matching_markers) > 1:
|
|
raise ValueError(
|
|
f"Each test should only be in one category. Test '{item.name}' is assigned "
|
|
f"to these categories: {matching_markers}. Please fix the "
|
|
"mapping in `PATH_PYTEST_MARKER_MAPPINGS`."
|
|
)
|
|
|
|
return matching_markers[0]
|
|
|
|
|
|
def pytest_collection_modifyitems(items: List[Function]) -> None:
|
|
"""Adds pytest markers dynamically when the tests are run.
|
|
|
|
This is automatically called by pytest during its execution.
|
|
|
|
Args:
|
|
items: Tests to be run.
|
|
"""
|
|
for item in items:
|
|
marker = _get_marker_for_ci_matrix(item)
|
|
item.add_marker(marker)
|
|
|
|
|
|
def create_test_file_with_size(directory: Path, size_in_mb: float) -> Path:
|
|
file_path = directory / uuid.uuid4().hex
|
|
with open(file_path, mode="wb") as f:
|
|
f.seek(int(1024 * 1024 * size_in_mb))
|
|
f.write(b"\0")
|
|
|
|
return file_path
|
|
|
|
|
|
@pytest.fixture()
|
|
def default_model_storage(tmp_path: Path, monkeypatch: MonkeyPatch) -> ModelStorage:
|
|
return LocalModelStorage.create(tmp_path)
|
|
|
|
|
|
@pytest.fixture()
|
|
def default_execution_context() -> ExecutionContext:
|
|
return ExecutionContext(GraphSchema({}), uuid.uuid4().hex)
|
|
|
|
|
|
@pytest.fixture(scope="session", autouse=True)
|
|
def temp_cache_for_fixtures(tmp_path_factory: TempPathFactory) -> None:
|
|
# This fixture makes sure that wide fixtures which don't have `function` scope
|
|
# (session, package, module) don't use the global
|
|
# cache. If you want to use the cache in a session scoped fixture, then please
|
|
# consider using the `enable_cache` context manager.
|
|
LocalTrainingCache._get_cache_location = lambda: tmp_path_factory.mktemp(
|
|
f"cache-{uuid.uuid4()}"
|
|
)
|
|
|
|
# We can omit reverting the monkeypatch as this fixture is torn down after all the
|
|
# tests ran
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def use_temp_dir_for_cache(
|
|
monkeypatch: MonkeyPatch, tmp_path_factory: TempdirFactory
|
|
) -> None:
|
|
# This fixture makes sure that a single test function has a constant cache
|
|
# cache.
|
|
cache_dir = tmp_path_factory.mktemp(uuid.uuid4().hex)
|
|
monkeypatch.setattr(LocalTrainingCache, "_get_cache_location", lambda: cache_dir)
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def enable_cache(cache_dir: Path):
|
|
old_get_cache_location = LocalTrainingCache._get_cache_location
|
|
LocalTrainingCache._get_cache_location = Mock(return_value=cache_dir)
|
|
|
|
yield
|
|
|
|
LocalTrainingCache._get_cache_location = old_get_cache_location
|
|
|
|
|
|
@pytest.fixture()
|
|
def whitespace_tokenizer() -> WhitespaceTokenizer:
|
|
return WhitespaceTokenizer(WhitespaceTokenizer.get_default_config())
|
|
|
|
|
|
def with_model_ids(events: List[Event], model_id: Text) -> List[Event]:
|
|
return [with_model_id(event, model_id) for event in events]
|
|
|
|
|
|
def with_model_id(event: Event, model_id: Text) -> Event:
|
|
new_event = copy.deepcopy(event)
|
|
new_event.metadata[METADATA_MODEL_ID] = model_id
|
|
return new_event
|
|
|
|
|
|
def with_assistant_id(event: Event, assistant_id: Text) -> Event:
|
|
event.metadata[ASSISTANT_ID_KEY] = assistant_id
|
|
return event
|
|
|
|
|
|
def with_assistant_ids(events: List[Event], assistant_id: Text) -> List[Event]:
|
|
return [with_assistant_id(event, assistant_id) for event in events]
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def sanic_test_mode(monkeypatch: MonkeyPatch):
|
|
monkeypatch.setattr(Sanic, "test_mode", True)
|
|
|
|
|
|
def filter_expected_warnings(records: WarningsRecorder) -> WarningsRecorder:
|
|
records_copy = copy.deepcopy(records.list)
|
|
|
|
for warning_type, warning_message in rasa.utils.common.EXPECTED_WARNINGS:
|
|
for record in records_copy:
|
|
if type(record.message) == warning_type and re.search(
|
|
warning_message, str(record.message)
|
|
):
|
|
records.pop(type(record.message))
|
|
|
|
return records
|
|
|
|
|
|
@pytest.fixture
|
|
def initial_events_including_restart() -> List[Event]:
|
|
return [
|
|
ActionExecuted(ACTION_SESSION_START_NAME, timestamp=1),
|
|
SessionStarted(timestamp=2),
|
|
ActionExecuted(ACTION_LISTEN_NAME, timestamp=3),
|
|
UserUttered("hi", timestamp=4),
|
|
ActionExecuted("utter_greet", timestamp=5),
|
|
ActionExecuted(ACTION_LISTEN_NAME, timestamp=6),
|
|
UserUttered("/restart", timestamp=7),
|
|
Restarted(timestamp=8),
|
|
ActionExecuted(ACTION_RESTART_NAME, timestamp=9),
|
|
]
|
|
|
|
|
|
@pytest.fixture
|
|
def events_after_restart() -> List[Event]:
|
|
return [
|
|
ActionExecuted(ACTION_SESSION_START_NAME, timestamp=10),
|
|
SessionStarted(timestamp=11),
|
|
ActionExecuted(ACTION_LISTEN_NAME, timestamp=12),
|
|
UserUttered("Let's start again.", timestamp=13),
|
|
]
|
|
|
|
|
|
@pytest.fixture
|
|
def tracker_with_restarted_event(
|
|
initial_events_including_restart: List[Event],
|
|
events_after_restart: List[Event],
|
|
) -> DialogueStateTracker:
|
|
sender_id = uuid.uuid4().hex
|
|
events = initial_events_including_restart + events_after_restart
|
|
|
|
return DialogueStateTracker.from_events(sender_id=sender_id, evts=events)
|