regrest


Nameregrest JSON
Version 0.2.0 PyPI version JSON
download
home_pageNone
SummaryRegression testing tool for Python
upload_time2025-10-26 23:20:31
maintainerNone
docs_urlNone
authorNone
requires_python>=3.9
licenseNone
keywords testing regression test regression-testing
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Regrest

[English](https://github.com/eycjur/regrest/blob/main/README.md) | [日本語](https://github.com/eycjur/regrest/blob/main/README_ja.md)

[![PyPI version](https://badge.fury.io/py/regrest.svg)](https://badge.fury.io/py/regrest)
[![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Test](https://github.com/eycjur/regrest/actions/workflows/test.yml/badge.svg)](https://github.com/eycjur/regrest/actions/workflows/test.yml)
[![Static Analysis](https://github.com/eycjur/regrest/actions/workflows/static_analysis.yml/badge.svg)](https://github.com/eycjur/regrest/actions/workflows/static_analysis.yml)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/eycjur/regrest)

**Regrest** is a simple and powerful regression testing tool for Python. It automatically records function outputs on the first run and validates them on subsequent runs.

## Features

- 🎯 **Simple decorator-based API** - Just add `@regrest` to your functions
- 📝 **Automatic recording** - First run records outputs, subsequent runs validate
- 🔍 **Smart comparison** - Handles floats, dicts, lists, nested structures, and custom classes
- 🛠 **CLI tools** - List, view, delete, and visualize test records
- 📊 **Web visualization** - Beautiful dashboard with JSONesque display, hierarchical navigation, and hot reload
- ⚙️ **Configurable** - Custom tolerance, storage location, and more
- 🔧 **Auto .gitignore** - Automatically creates `.regrest/.gitignore` to exclude test records on first run

## Requirements

- Python 3.9 or higher

## Installation

```bash
pip install regrest

# Optional: Install with Flask for better server performance
pip install regrest[server]
```

## Quick Start

### Basic Usage

```python
from regrest import regrest

@regrest
def calculate_price(items, discount=0):
    total = sum(item['price'] for item in items)
    return total * (1 - discount)

# First run: records the result
items = [{'price': 100}, {'price': 200}]
result = calculate_price(items, discount=0.1)  # Returns 270.0, records it
# Output: [regrest] Recorded: __main__.calculate_price (id: abc123...)

# Second run: validates against recorded result
result = calculate_price(items, discount=0.1)  # Returns 270.0, compares with record
# Output: [regrest] Passed: __main__.calculate_price (id: abc123...)
```

### Custom Tolerance

```python
@regrest(tolerance=1e-6)
def calculate_pi():
    return 3.14159265359
```

### Update Mode

To update existing records instead of testing:

```python
@regrest(update=True)
def my_function():
    return "new result"
```

Or set the environment variable:

```bash
REGREST_UPDATE_MODE=1 python your_script.py
```

## Environment Variables

| Variable | Description | Values | Default |
|----------|-------------|--------|---------|
| `REGREST_LOG_LEVEL` | Log level | DEBUG, INFO, WARNING, ERROR, CRITICAL | `INFO` |
| `REGREST_RAISE_ON_ERROR` | Raise exceptions on test failure | True/False | `False` |
| `REGREST_UPDATE_MODE` | Update all records | True/False | `False` |
| `REGREST_STORAGE_DIR` | Custom storage directory | Directory path | `.regrest` |
| `REGREST_FLOAT_TOLERANCE` | Float comparison tolerance | Numeric value | `1e-9` |

**Priority**: Constructor arguments > Environment variables > Default values

## CLI Commands

### List Records

```bash
regrest list              # List all records
regrest list -k calculate # Filter by keyword
```

Lists all test records with module, function, arguments, results, and timestamps.

### Delete Records

```bash
regrest delete abc123def456      # Delete by ID
regrest delete --pattern "test_*" # Delete by pattern
regrest delete --all             # Delete all records
```

### Serve Web UI

```bash
regrest serve                    # Start on localhost:8000
regrest serve --port 8080        # Custom port
regrest serve --host 0.0.0.0     # Allow external access
regrest serve --reload           # Enable hot reload
```

Access the web UI at `http://localhost:8000` for:
- **Hierarchical view** - Organized by module → function → record
- **Search & filter** - Find records by keyword
- **JSONesque display** - Syntax-highlighted, readable format
- **Record management** - Delete individual or all records

## Architecture

### System Overview

```mermaid
graph TB
    subgraph "User Code"
        A[Decorated Function<br/>@regrest]
    end

    subgraph "Regrest Core"
        B[Decorator<br/>decorator.py]
        C[Storage<br/>storage.py]
        D[Matcher<br/>matcher.py]
        E[Config<br/>config.py]
        F[Exceptions<br/>exceptions.py]
    end

    subgraph "Storage Layer"
        G[JSON Files<br/>.regrest/*.json]
        H[Pickle Serialization<br/>base64 encoded]
    end

    subgraph "CLI & Server"
        I[CLI<br/>typer-based]
        J[Web Server<br/>Flask/HTTP]
        K[Web UI<br/>Tailwind CSS]
    end

    A --> B
    B --> C
    B --> D
    B --> E
    B --> F
    C --> G
    C --> H
    I --> C
    J --> C
    J --> K

    style A fill:#e1f5ff
    style B fill:#fff4e1
    style C fill:#f0e1ff
    style D fill:#e1ffe1
    style I fill:#ffe1e1
    style J fill:#ffe1e1
```

## How It Works

1. **First Run**: When you call a function decorated with `@regrest`, it executes normally and saves:
   - Module and function name
   - Arguments (args and kwargs)
   - Return value
   - Timestamp

   The record is saved to `.regrest/` directory as a JSON file.

2. **Subsequent Runs**: On the next call with the same arguments:
   - The function executes
   - The result is compared with the recorded value
   - If they match → Test passes ✅
   - If they don't match → `RegressionTestError` is raised ❌

3. **Update Mode**: When you need to update the expected values:
   - Use `@regrest(update=True)` or `REGREST_UPDATE_MODE=1`
   - The old record is replaced with the new result

## Configuration

| Level | Usage | Example |
|-------|-------|---------|
| **Global** | Configure all tests | `from regrest import Config, set_config`<br>`config = Config(storage_dir='.my_records', float_tolerance=1e-6)`<br>`set_config(config)` |
| **Per-function** | Configure specific function | `@regrest(tolerance=1e-9)`<br>`def precise_calculation():`<br>`    return 3.141592653589793` |

## Advanced Features

### Comparison Logic

The matcher intelligently compares:
- **Primitives**: Exact match for strings, booleans
- **Numbers**: Tolerance-based for floats, exact for integers
- **Collections**: Deep comparison for lists, dicts, sets
- **Nested structures**: Recursive comparison with detailed error messages

## Storage Format

### File Structure

Records are stored as JSON files in the `.regrest/` directory:

```
.regrest/
├── .gitignore                                    # Auto-generated
├── example.calculate_price.a1b2c3d4.json       # Record file
└── mymodule.process_data.e5f6g7h8.json        # Record file
```

### File Naming Convention

`{module}.{function}.{record_id}.json`

| Component | Description | Example |
|-----------|-------------|---------|
| `module` | Module name where function is defined | `example`, `mymodule` |
| `function` | Function name | `calculate_price`, `process_data` |
| `record_id` | SHA256 hash of arguments (first 16 chars) | `a1b2c3d4e5f6g7h8` |

**Record ID Generation**: Records are uniquely identified by:
1. Module name
2. Function name
3. SHA256 hash of serialized arguments (args + kwargs)

This means **different argument combinations create separate records** for the same function.

### Encoding Strategy

Regrest uses a **hybrid encoding** approach for maximum compatibility and readability:

| Data Type | Storage Method | Readable | Example |
|-----------|---------------|----------|---------|
| **JSON-serializable**<br>(int, float, str, bool, list, dict, None) | JSON | ✅ Yes | `{"result": {"type": "json", "data": 270.0}}` |
| **Non-JSON-serializable**<br>(custom classes, complex objects) | Pickle + Base64 | ❌ No | `{"result": {"type": "pickle", "data": "gASV..."}}` |

**Advantages**:
- ✅ **Readable**: Simple data types are stored as JSON for easy inspection
- ✅ **Flexible**: Complex objects are automatically pickled
- ✅ **Version control friendly**: JSON format produces clean diffs

**Considerations**:
- ⚠️ **Pickle compatibility**: May have issues across different Python versions
- ⚠️ **Custom classes**: Must be pickle-serializable and implement `__eq__` for comparison

## Best Practices

| Practice | Description |
|----------|-------------|
| **Deterministic functions** | Use `@regrest` only on functions with consistent outputs (same input → same output) |
| **Auto .gitignore** | Test records are automatically excluded from git via `.regrest/.gitignore` |
| **Update workflow** | When intentionally changing behavior, update records: `REGREST_UPDATE_MODE=1 python script.py` |

## Limitations

| Limitation | Description |
|------------|-------------|
| **Python version** | Requires Python 3.9+ |
| **Non-deterministic functions** | Don't use on functions with random outputs, timestamps, or external API calls |
| **Serialization** | Data must be JSON or pickle-serializable; custom classes need `__eq__` for comparison |
| **Pickle compatibility** | May have issues across different Python versions |

## Contributing

Contributions welcome! Run `make check` before submitting PRs.

## License

MIT License

## Changelog

### 0.1.0 (Initial Release)
- **Published to PyPI** - Install with `pip install regrest`
- Core decorator functionality (`@regrest`)
- Hybrid JSON/Pickle storage system
- Smart comparison with floating-point tolerance
- CLI tools (`regrest list`, `regrest delete`)
- Custom class support
- Auto `.gitignore` generation
- Environment variable configuration
- Colorful logging output
- Python 3.9+ support
- pyproject.toml-based build system
- ruff + mypy static analysis
- Makefile task automation
- GitHub Actions CI/CD

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "regrest",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.9",
    "maintainer_email": null,
    "keywords": "testing, regression, test, regression-testing",
    "author": null,
    "author_email": "Katsuhiro Muto <63308909+eycjur@users.noreply.github.com>",
    "download_url": "https://files.pythonhosted.org/packages/6c/8a/bc26fe45923b917bc84a16a39b1919088f50db76640102dcdf1f9a23dbee/regrest-0.2.0.tar.gz",
    "platform": null,
    "description": "# Regrest\n\n[English](https://github.com/eycjur/regrest/blob/main/README.md) | [\u65e5\u672c\u8a9e](https://github.com/eycjur/regrest/blob/main/README_ja.md)\n\n[![PyPI version](https://badge.fury.io/py/regrest.svg)](https://badge.fury.io/py/regrest)\n[![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Test](https://github.com/eycjur/regrest/actions/workflows/test.yml/badge.svg)](https://github.com/eycjur/regrest/actions/workflows/test.yml)\n[![Static Analysis](https://github.com/eycjur/regrest/actions/workflows/static_analysis.yml/badge.svg)](https://github.com/eycjur/regrest/actions/workflows/static_analysis.yml)\n[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/eycjur/regrest)\n\n**Regrest** is a simple and powerful regression testing tool for Python. It automatically records function outputs on the first run and validates them on subsequent runs.\n\n## Features\n\n- \ud83c\udfaf **Simple decorator-based API** - Just add `@regrest` to your functions\n- \ud83d\udcdd **Automatic recording** - First run records outputs, subsequent runs validate\n- \ud83d\udd0d **Smart comparison** - Handles floats, dicts, lists, nested structures, and custom classes\n- \ud83d\udee0 **CLI tools** - List, view, delete, and visualize test records\n- \ud83d\udcca **Web visualization** - Beautiful dashboard with JSONesque display, hierarchical navigation, and hot reload\n- \u2699\ufe0f **Configurable** - Custom tolerance, storage location, and more\n- \ud83d\udd27 **Auto .gitignore** - Automatically creates `.regrest/.gitignore` to exclude test records on first run\n\n## Requirements\n\n- Python 3.9 or higher\n\n## Installation\n\n```bash\npip install regrest\n\n# Optional: Install with Flask for better server performance\npip install regrest[server]\n```\n\n## Quick Start\n\n### Basic Usage\n\n```python\nfrom regrest import regrest\n\n@regrest\ndef calculate_price(items, discount=0):\n    total = sum(item['price'] for item in items)\n    return total * (1 - discount)\n\n# First run: records the result\nitems = [{'price': 100}, {'price': 200}]\nresult = calculate_price(items, discount=0.1)  # Returns 270.0, records it\n# Output: [regrest] Recorded: __main__.calculate_price (id: abc123...)\n\n# Second run: validates against recorded result\nresult = calculate_price(items, discount=0.1)  # Returns 270.0, compares with record\n# Output: [regrest] Passed: __main__.calculate_price (id: abc123...)\n```\n\n### Custom Tolerance\n\n```python\n@regrest(tolerance=1e-6)\ndef calculate_pi():\n    return 3.14159265359\n```\n\n### Update Mode\n\nTo update existing records instead of testing:\n\n```python\n@regrest(update=True)\ndef my_function():\n    return \"new result\"\n```\n\nOr set the environment variable:\n\n```bash\nREGREST_UPDATE_MODE=1 python your_script.py\n```\n\n## Environment Variables\n\n| Variable | Description | Values | Default |\n|----------|-------------|--------|---------|\n| `REGREST_LOG_LEVEL` | Log level | DEBUG, INFO, WARNING, ERROR, CRITICAL | `INFO` |\n| `REGREST_RAISE_ON_ERROR` | Raise exceptions on test failure | True/False | `False` |\n| `REGREST_UPDATE_MODE` | Update all records | True/False | `False` |\n| `REGREST_STORAGE_DIR` | Custom storage directory | Directory path | `.regrest` |\n| `REGREST_FLOAT_TOLERANCE` | Float comparison tolerance | Numeric value | `1e-9` |\n\n**Priority**: Constructor arguments > Environment variables > Default values\n\n## CLI Commands\n\n### List Records\n\n```bash\nregrest list              # List all records\nregrest list -k calculate # Filter by keyword\n```\n\nLists all test records with module, function, arguments, results, and timestamps.\n\n### Delete Records\n\n```bash\nregrest delete abc123def456      # Delete by ID\nregrest delete --pattern \"test_*\" # Delete by pattern\nregrest delete --all             # Delete all records\n```\n\n### Serve Web UI\n\n```bash\nregrest serve                    # Start on localhost:8000\nregrest serve --port 8080        # Custom port\nregrest serve --host 0.0.0.0     # Allow external access\nregrest serve --reload           # Enable hot reload\n```\n\nAccess the web UI at `http://localhost:8000` for:\n- **Hierarchical view** - Organized by module \u2192 function \u2192 record\n- **Search & filter** - Find records by keyword\n- **JSONesque display** - Syntax-highlighted, readable format\n- **Record management** - Delete individual or all records\n\n## Architecture\n\n### System Overview\n\n```mermaid\ngraph TB\n    subgraph \"User Code\"\n        A[Decorated Function<br/>@regrest]\n    end\n\n    subgraph \"Regrest Core\"\n        B[Decorator<br/>decorator.py]\n        C[Storage<br/>storage.py]\n        D[Matcher<br/>matcher.py]\n        E[Config<br/>config.py]\n        F[Exceptions<br/>exceptions.py]\n    end\n\n    subgraph \"Storage Layer\"\n        G[JSON Files<br/>.regrest/*.json]\n        H[Pickle Serialization<br/>base64 encoded]\n    end\n\n    subgraph \"CLI & Server\"\n        I[CLI<br/>typer-based]\n        J[Web Server<br/>Flask/HTTP]\n        K[Web UI<br/>Tailwind CSS]\n    end\n\n    A --> B\n    B --> C\n    B --> D\n    B --> E\n    B --> F\n    C --> G\n    C --> H\n    I --> C\n    J --> C\n    J --> K\n\n    style A fill:#e1f5ff\n    style B fill:#fff4e1\n    style C fill:#f0e1ff\n    style D fill:#e1ffe1\n    style I fill:#ffe1e1\n    style J fill:#ffe1e1\n```\n\n## How It Works\n\n1. **First Run**: When you call a function decorated with `@regrest`, it executes normally and saves:\n   - Module and function name\n   - Arguments (args and kwargs)\n   - Return value\n   - Timestamp\n\n   The record is saved to `.regrest/` directory as a JSON file.\n\n2. **Subsequent Runs**: On the next call with the same arguments:\n   - The function executes\n   - The result is compared with the recorded value\n   - If they match \u2192 Test passes \u2705\n   - If they don't match \u2192 `RegressionTestError` is raised \u274c\n\n3. **Update Mode**: When you need to update the expected values:\n   - Use `@regrest(update=True)` or `REGREST_UPDATE_MODE=1`\n   - The old record is replaced with the new result\n\n## Configuration\n\n| Level | Usage | Example |\n|-------|-------|---------|\n| **Global** | Configure all tests | `from regrest import Config, set_config`<br>`config = Config(storage_dir='.my_records', float_tolerance=1e-6)`<br>`set_config(config)` |\n| **Per-function** | Configure specific function | `@regrest(tolerance=1e-9)`<br>`def precise_calculation():`<br>`    return 3.141592653589793` |\n\n## Advanced Features\n\n### Comparison Logic\n\nThe matcher intelligently compares:\n- **Primitives**: Exact match for strings, booleans\n- **Numbers**: Tolerance-based for floats, exact for integers\n- **Collections**: Deep comparison for lists, dicts, sets\n- **Nested structures**: Recursive comparison with detailed error messages\n\n## Storage Format\n\n### File Structure\n\nRecords are stored as JSON files in the `.regrest/` directory:\n\n```\n.regrest/\n\u251c\u2500\u2500 .gitignore                                    # Auto-generated\n\u251c\u2500\u2500 example.calculate_price.a1b2c3d4.json       # Record file\n\u2514\u2500\u2500 mymodule.process_data.e5f6g7h8.json        # Record file\n```\n\n### File Naming Convention\n\n`{module}.{function}.{record_id}.json`\n\n| Component | Description | Example |\n|-----------|-------------|---------|\n| `module` | Module name where function is defined | `example`, `mymodule` |\n| `function` | Function name | `calculate_price`, `process_data` |\n| `record_id` | SHA256 hash of arguments (first 16 chars) | `a1b2c3d4e5f6g7h8` |\n\n**Record ID Generation**: Records are uniquely identified by:\n1. Module name\n2. Function name\n3. SHA256 hash of serialized arguments (args + kwargs)\n\nThis means **different argument combinations create separate records** for the same function.\n\n### Encoding Strategy\n\nRegrest uses a **hybrid encoding** approach for maximum compatibility and readability:\n\n| Data Type | Storage Method | Readable | Example |\n|-----------|---------------|----------|---------|\n| **JSON-serializable**<br>(int, float, str, bool, list, dict, None) | JSON | \u2705 Yes | `{\"result\": {\"type\": \"json\", \"data\": 270.0}}` |\n| **Non-JSON-serializable**<br>(custom classes, complex objects) | Pickle + Base64 | \u274c No | `{\"result\": {\"type\": \"pickle\", \"data\": \"gASV...\"}}` |\n\n**Advantages**:\n- \u2705 **Readable**: Simple data types are stored as JSON for easy inspection\n- \u2705 **Flexible**: Complex objects are automatically pickled\n- \u2705 **Version control friendly**: JSON format produces clean diffs\n\n**Considerations**:\n- \u26a0\ufe0f **Pickle compatibility**: May have issues across different Python versions\n- \u26a0\ufe0f **Custom classes**: Must be pickle-serializable and implement `__eq__` for comparison\n\n## Best Practices\n\n| Practice | Description |\n|----------|-------------|\n| **Deterministic functions** | Use `@regrest` only on functions with consistent outputs (same input \u2192 same output) |\n| **Auto .gitignore** | Test records are automatically excluded from git via `.regrest/.gitignore` |\n| **Update workflow** | When intentionally changing behavior, update records: `REGREST_UPDATE_MODE=1 python script.py` |\n\n## Limitations\n\n| Limitation | Description |\n|------------|-------------|\n| **Python version** | Requires Python 3.9+ |\n| **Non-deterministic functions** | Don't use on functions with random outputs, timestamps, or external API calls |\n| **Serialization** | Data must be JSON or pickle-serializable; custom classes need `__eq__` for comparison |\n| **Pickle compatibility** | May have issues across different Python versions |\n\n## Contributing\n\nContributions welcome! Run `make check` before submitting PRs.\n\n## License\n\nMIT License\n\n## Changelog\n\n### 0.1.0 (Initial Release)\n- **Published to PyPI** - Install with `pip install regrest`\n- Core decorator functionality (`@regrest`)\n- Hybrid JSON/Pickle storage system\n- Smart comparison with floating-point tolerance\n- CLI tools (`regrest list`, `regrest delete`)\n- Custom class support\n- Auto `.gitignore` generation\n- Environment variable configuration\n- Colorful logging output\n- Python 3.9+ support\n- pyproject.toml-based build system\n- ruff + mypy static analysis\n- Makefile task automation\n- GitHub Actions CI/CD\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "Regression testing tool for Python",
    "version": "0.2.0",
    "project_urls": {
        "Documentation": "https://github.com/eycjur/regrest#readme",
        "Homepage": "https://github.com/eycjur/regrest",
        "Issues": "https://github.com/eycjur/regrest/issues",
        "PyPI": "https://pypi.org/project/regrest/",
        "Repository": "https://github.com/eycjur/regrest"
    },
    "split_keywords": [
        "testing",
        " regression",
        " test",
        " regression-testing"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "5999cf2bac8b848abd72a06441cd1b47d05e22f937461d11dd301e43e92247db",
                "md5": "47ee07069ac8f9752a09dbac1e6f8b4e",
                "sha256": "77921b1418c6735825b4a5c6ea17577e399eb2daebbf477699df58a3eaf97fe4"
            },
            "downloads": -1,
            "filename": "regrest-0.2.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "47ee07069ac8f9752a09dbac1e6f8b4e",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.9",
            "size": 24364,
            "upload_time": "2025-10-26T23:20:29",
            "upload_time_iso_8601": "2025-10-26T23:20:29.833410Z",
            "url": "https://files.pythonhosted.org/packages/59/99/cf2bac8b848abd72a06441cd1b47d05e22f937461d11dd301e43e92247db/regrest-0.2.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "6c8abc26fe45923b917bc84a16a39b1919088f50db76640102dcdf1f9a23dbee",
                "md5": "b1e2f7cf4ffb0a8ba76c591d052118ef",
                "sha256": "b8a449bcb406e8c9c0e6f9a2f80b78da9040f4601ec2520f7713d96e8074e434"
            },
            "downloads": -1,
            "filename": "regrest-0.2.0.tar.gz",
            "has_sig": false,
            "md5_digest": "b1e2f7cf4ffb0a8ba76c591d052118ef",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9",
            "size": 26873,
            "upload_time": "2025-10-26T23:20:31",
            "upload_time_iso_8601": "2025-10-26T23:20:31.270106Z",
            "url": "https://files.pythonhosted.org/packages/6c/8a/bc26fe45923b917bc84a16a39b1919088f50db76640102dcdf1f9a23dbee/regrest-0.2.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-10-26 23:20:31",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "eycjur",
    "github_project": "regrest#readme",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "regrest"
}
        
Elapsed time: 1.58323s