Skip to content

Commit

Permalink
feat(ci): run tests against multiple versions (#51)
Browse files Browse the repository at this point in the history
* feat(ci): run tests against multiple versions

* add uuid to test

* fix and lint

* fix table name

* fix type

* Update DEVELOPER.md

* Update DEVELOPER.md
  • Loading branch information
averikitsch committed Mar 18, 2024
1 parent bc45740 commit 3439c9d
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 22 deletions.
6 changes: 5 additions & 1 deletion .github/sync-repo-settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ branchProtectionRules:
requiredStatusCheckContexts:
- "cla/google"
- "lint"
- "mysql-integration-test-pr (langchain-cloud-sql-testing)"
- "mysql-integration-test-pr-py38 (langchain-cloud-sql-testing)"
- "mysql-integration-test-pr-py39 (langchain-cloud-sql-testing)"
- "mysql-integration-test-pr-py310 (langchain-cloud-sql-testing)"
- "mysql-integration-test-pr-py311 (langchain-cloud-sql-testing)"
- "mysql-integration-test-pr-py312 (langchain-cloud-sql-testing)"
- "conventionalcommits.org"
- "header-check"
# - Add required status checks like presubmit tests
Expand Down
81 changes: 81 additions & 0 deletions DEVELOPER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# DEVELOPER.md

## Versioning

This library follows [Semantic Versioning](http://semver.org/).

## Processes

### Conventional Commit messages

This repository uses tool [Release Please](https://github.com/googleapis/release-please) to create GitHub and PyPi releases. It does so by parsing your
git history, looking for [Conventional Commit messages](https://www.conventionalcommits.org/),
and creating release PRs.

Learn more by reading [How should I write my commits?](https://github.com/googleapis/release-please?tab=readme-ov-file#how-should-i-write-my-commits)

## Testing

### Run tests locally

1. Set environment variables for `INSTANCE_ID`, `DB_NAME`, `TABLE_NAME`, `REGION`, `DB_USER`, `DB_PASSWORD`

1. Run pytest to automatically run all tests:

```bash
pytest
```

### CI Platform Setup

Cloud Build is used to run tests against Google Cloud resources in test project: langchain-cloud-sql-testing.
Each test has a corresponding Cloud Build trigger, see [all triggers][triggers].
These tests are registered as required tests in `.github/sync-repo-settings.yaml`.

#### Trigger Setup

Cloud Build triggers (for Python versions 3.8 to 3.11) were created with the following specs:

```YAML
name: mysql-integration-test-pr-py38
description: Run integration tests on PR for Python 3.8
filename: integration.cloudbuild.yaml
github:
name: langchain-google-cloud-sql-mysql-python
owner: googleapis
pullRequest:
branch: .*
commentControl: COMMENTS_ENABLED_FOR_EXTERNAL_CONTRIBUTORS_ONLY
ignoredFiles:
- docs/**
- .kokoro/**
- .github/**
- "*.md"
substitutions:
_INSTANCE_ID: <ADD_VALUE>
_DB_NAME: <ADD_VALUE>
_REGION: us-central1
_VERSION: "3.8"
```

Use `gcloud builds triggers import --source=trigger.yaml` create triggers via the command line

#### Project Setup

1. Create an Cloud SQL for PostgreSQL instance and database
1. Setup Cloud Build triggers (above)

#### Run tests with Cloud Build

* Run integration test:

```bash
gcloud builds submit --config integration.cloudbuild.yaml --region us-central1 --substitutions=_INSTANCE_ID=$INSTANCE_ID,_DB_NAME=$DB_NAME,_REGION=$REGION
```

#### Trigger

To run Cloud Build tests on GitHub from external contributors, ie RenovateBot, comment: `/gcbrun`.


[triggers]: https://console.cloud.google.com/cloud-build/triggers?e=13802955&project=langchain-cloud-sql-testing
30 changes: 17 additions & 13 deletions integration.cloudbuild.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,39 @@

steps:
- id: Install dependencies
name: python:3.11
name: python:${_VERSION}
entrypoint: pip
args: ["install", "--user", "-r", "requirements.txt"]

- id: Install module (and test requirements)
name: python:3.11
name: python:${_VERSION}
entrypoint: pip
args: ["install", ".[test]", "--user"]

- id: Run integration tests
name: python:3.11
name: python:${_VERSION}
entrypoint: python
args: ["-m", "pytest"]
env:
- 'PROJECT_ID=$PROJECT_ID'
- 'INSTANCE_ID=$_INSTANCE_ID'
- 'DB_NAME=$_DB_NAME'
- 'TABLE_NAME=test-$BUILD_ID'
- 'REGION=$_REGION'
secretEnv: ['DB_USER', 'DB_PASSWORD']
- "PROJECT_ID=$PROJECT_ID"
- "INSTANCE_ID=$_INSTANCE_ID"
- "DB_NAME=$_DB_NAME"
- "TABLE_NAME=test-$BUILD_ID"
- "REGION=$_REGION"
secretEnv: ["DB_USER", "DB_PASSWORD"]

availableSecrets:
secretManager:
- versionName: projects/$PROJECT_ID/secrets/langchain-test-mysql-username/versions/1
env: 'DB_USER'
- versionName: projects/$PROJECT_ID/secrets/langchain-test-mysql-password/versions/1
env: 'DB_PASSWORD'
- versionName: projects/$PROJECT_ID/secrets/langchain-test-mysql-username/versions/1
env: "DB_USER"
- versionName: projects/$PROJECT_ID/secrets/langchain-test-mysql-password/versions/1
env: "DB_PASSWORD"

substitutions:
_INSTANCE_ID: test-instance
_REGION: us-central1
_DB_NAME: test
_VERSION: "3.8"

options:
dynamicSubstitutions: true
3 changes: 1 addition & 2 deletions src/langchain_google_cloud_sql_mysql/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import json
from collections.abc import Iterable
from typing import Any, Dict, Iterator, List, Optional, Sequence, cast
from typing import Any, Dict, Iterable, Iterator, List, Optional, cast

import pymysql
import sqlalchemy
Expand Down
21 changes: 15 additions & 6 deletions tests/integration/test_mysql_chat_message_history.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import uuid
from typing import Generator

import pytest
Expand All @@ -25,17 +26,21 @@
region = os.environ["REGION"]
instance_id = os.environ["INSTANCE_ID"]
db_name = os.environ["DB_NAME"]
table_name = "message_store"
table_name = "message_store" + str(uuid.uuid4())
malformed_table = "malformed_table" + str(uuid.uuid4())


@pytest.fixture(name="memory_engine")
def setup() -> Generator:
engine = MySQLEngine.from_instance(
project_id=project_id, region=region, instance=instance_id, database=db_name
project_id=project_id,
region=region,
instance=instance_id,
database=db_name,
)

# create table with malformed schema (missing 'type')
query = """CREATE TABLE malformed_table (
query = f"""CREATE TABLE `{malformed_table}` (
id INT AUTO_INCREMENT PRIMARY KEY,
session_id TEXT NOT NULL,
data JSON NOT NULL
Expand All @@ -47,7 +52,7 @@ def setup() -> Generator:
# use default table for MySQLChatMessageHistory
with engine.connect() as conn:
conn.execute(sqlalchemy.text(f"DROP TABLE IF EXISTS `{table_name}`"))
conn.execute(sqlalchemy.text(f"DROP TABLE IF EXISTS malformed_table"))
conn.execute(sqlalchemy.text(f"DROP TABLE IF EXISTS `{malformed_table}`"))
conn.commit()


Expand All @@ -71,7 +76,9 @@ def test_chat_message_history(memory_engine: MySQLEngine) -> None:
assert len(history.messages) == 0


def test_chat_message_history_table_does_not_exist(memory_engine: MySQLEngine) -> None:
def test_chat_message_history_table_does_not_exist(
memory_engine: MySQLEngine,
) -> None:
"""Test that MySQLChatMessageHistory fails if table does not exist."""
with pytest.raises(AttributeError) as exc_info:
MySQLChatMessageHistory(
Expand All @@ -90,5 +97,7 @@ def test_chat_message_history_table_malformed_schema(
"""Test that MySQLChatMessageHistory fails if schema is malformed."""
with pytest.raises(IndexError):
MySQLChatMessageHistory(
engine=memory_engine, session_id="test", table_name="malformed_table"
engine=memory_engine,
session_id="test",
table_name=malformed_table,
)

0 comments on commit 3439c9d

Please sign in to comment.