Skip to content

Commit dcdd18b

Browse files
aaronsteersCopilot
andauthored
tests: add docker-based tests to FAST standard tests (#586)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 5797a2f commit dcdd18b

File tree

19 files changed

+1215
-527
lines changed

19 files changed

+1215
-527
lines changed

.github/workflows/connector-tests.yml

Lines changed: 76 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -72,28 +72,23 @@ jobs:
7272
cdk_extra: n/a
7373
- connector: source-shopify
7474
cdk_extra: n/a
75-
# Chargebee is being flaky:
76-
# - connector: source-chargebee
77-
# cdk_extra: n/a
78-
# This one is behind in CDK updates and can't be used as tests until it is updated:
79-
# - connector: destination-pinecone
80-
# cdk_extra: vector-db-based
8175
- connector: source-google-drive
8276
cdk_extra: file-based
8377
- connector: destination-motherduck
8478
cdk_extra: sql
85-
# ZenDesk currently failing (as of 2024-12-02)
86-
# TODO: Re-enable once fixed
87-
# - connector: source-zendesk-support
88-
# cdk_extra: n/a
89-
# TODO: These are manifest connectors and won't work as expected until we
90-
# add `--use-local-cdk` support for manifest connectors.
9179
- connector: source-amplitude
9280
cdk_extra: n/a
81+
- connector: source-intercom
82+
cdk_extra: n/a
9383
- connector: source-pokeapi
9484
cdk_extra: n/a
9585

96-
name: "Check: '${{matrix.connector}}' (skip=${{needs.cdk_changes.outputs['src'] == 'false' || needs.cdk_changes.outputs[matrix.cdk_extra] == 'false'}})"
86+
# CDK Tests cannot build the Connector object (constructor args not optional).
87+
# - connector: source-zendesk-support
88+
# cdk_extra: n/a
89+
90+
name: "${{ needs.cdk_changes.outputs[matrix.cdk_extra] == 'false' && 'Skipped Check' || 'Check' }}: ${{matrix.connector}}"
91+
if: needs.cdk_changes.outputs['src'] == 'true'
9792
permissions:
9893
checks: write
9994
contents: write # Required for creating commit statuses
@@ -125,110 +120,89 @@ jobs:
125120
repository: airbytehq/airbyte
126121
ref: master
127122
path: airbyte
123+
128124
- name: Set up Python
125+
if: steps.no_changes.outputs.status != 'cancelled'
129126
uses: actions/setup-python@v5
130127
with:
131128
python-version: "3.11"
132-
# Create initial pending status for test report
133-
- name: Create Pending Test Report Status
129+
130+
- name: Set up `uv`
131+
if: steps.no_changes.outputs.status != 'cancelled'
132+
uses: astral-sh/setup-uv@v6.1.0
133+
134+
- name: Set up `poe`
135+
if: steps.no_changes.outputs.status != 'cancelled'
136+
run: |
137+
uv tool install poethepoet
138+
139+
- name: Set up Poetry
140+
if: steps.no_changes.outputs.status != 'cancelled'
141+
uses: Gr1N/setup-poetry@v9
142+
with:
143+
poetry-version: "2.0.1"
144+
145+
- name: Get Connector Language
146+
if: steps.no_changes.outputs.status != 'cancelled'
147+
working-directory: airbyte/airbyte-integrations/connectors/${{ matrix.connector }}
148+
run: |
149+
# Get the language of the connector from the metadata file
150+
CONNECTOR_LANGUAGE=$(poe -qq get-language)
151+
echo "CONNECTOR_LANGUAGE=$CONNECTOR_LANGUAGE" | tee -a $GITHUB_ENV
152+
153+
- name: Install CDK with Poetry
134154
if: steps.no_changes.outputs.status != 'cancelled'
135-
env:
136-
GH_TOKEN: ${{ secrets.GH_PAT_MAINTENANCE_OCTAVIA }}
137155
run: |
138-
HEAD_SHA="${{ github.event.pull_request.head.sha || github.sha }}"
139-
gh api \
140-
--method POST \
141-
-H "Accept: application/vnd.github+json" \
142-
-H "X-GitHub-Api-Version: 2022-11-28" \
143-
repos/${{ github.repository }}/statuses/$HEAD_SHA \
144-
-f state="pending" \
145-
-f description="Running connector tests..." \
146-
-f context="${{ matrix.connector }} Test Report"
147-
148-
- name: Test Connector
156+
cd airbyte-python-cdk
157+
poetry install --all-extras
158+
159+
- name: Fetch Connector Secrets
149160
if: steps.no_changes.outputs.status != 'cancelled'
150161
timeout-minutes: 90
162+
working-directory: airbyte-python-cdk
151163
env:
152164
GCP_GSM_CREDENTIALS: ${{ secrets.GCP_GSM_CREDENTIALS }}
153165
POETRY_DYNAMIC_VERSIONING_BYPASS: "0.0.0"
154166
run: |
155-
cd airbyte
156-
make tools.airbyte-ci.install
157-
airbyte-ci \
158-
--ci-report-bucket-name=airbyte-ci-reports-multi \
159-
connectors \
160-
--name ${{matrix.connector}} \
161-
--use-local-cdk \
162-
test \
163-
--fail-fast \
164-
--skip-step qa_checks \
165-
--skip-step connector_live_tests
166-
167-
- name: Evaluate Test
168-
id: evaluate_output
169-
if: always() && steps.no_changes.outputs.status != 'cancelled'
167+
poetry run airbyte-cdk secrets fetch ${{ matrix.connector }}
168+
169+
- name: Bump to Dev Branch CDK [Python Connectors]
170+
if: env.CONNECTOR_LANGUAGE == 'python'
171+
working-directory: airbyte/airbyte-integrations/connectors/${{ matrix.connector }}
170172
run: |
171-
# save job output json file as ci step output
172-
json_output_file=$(find airbyte/airbyte-ci/connectors/pipelines/pipeline_reports -name 'output.json' -print -quit)
173-
job_output=$(cat ${json_output_file})
174-
success=$(echo ${job_output} | jq -r '.success')
175-
failed_step=$(echo ${job_output} | jq -r '.failed_steps | select(length > 0) | .[0] // "None"')
176-
run_duration=$(echo ${job_output} | jq -r '.run_duration')
177-
html_report_url=$(echo ${job_output} | jq -r '.html_report_url')
178-
echo "## Job Output for ${{matrix.connector}}" >> $GITHUB_STEP_SUMMARY
179-
echo "- [HTML Report](${html_report_url})" >> $GITHUB_STEP_SUMMARY
180-
echo "- Success: ${success}" >> $GITHUB_STEP_SUMMARY
181-
echo "- Test Duration: $(printf "%.0f" ${run_duration})s" >> $GITHUB_STEP_SUMMARY
182-
if [ "${success}" != "true" ]; then
183-
echo "- Failed Step: ${failed_step}" >> $GITHUB_STEP_SUMMARY
184-
fi
185-
echo -e "\n[Download Job Output](${{steps.upload_job_output.outputs.artifact-url}})" >> $GITHUB_STEP_SUMMARY
186-
if [ "${success}" != "true" ]; then
187-
echo "::error::Test failed for connector '${{ matrix.connector }}' on step '${failed_step}'. "
188-
exit 1
189-
fi
190-
echo "See the execution report for details: ${html_report_url}"
191-
echo "success=${success}" >> $GITHUB_OUTPUT
192-
echo "html_report_url=${html_report_url}" >> $GITHUB_OUTPUT
173+
echo "Using CDK ref ${{ github.event.pull_request.head.sha || github.sha }}"
174+
poe use-cdk-branch ${{ github.event.pull_request.head.sha || github.sha }}
175+
poetry install --all-extras
176+
177+
- name: Run Unit Tests [Python Connectors]
178+
if: env.CONNECTOR_LANGUAGE == 'python'
179+
working-directory: airbyte/airbyte-integrations/connectors/${{ matrix.connector }}
180+
run: |
181+
poe test-unit-tests
182+
183+
- name: Run FAST Standard Tests [Python Connectors]
184+
if: env.CONNECTOR_LANGUAGE == 'python'
185+
working-directory: airbyte/airbyte-integrations/connectors/${{ matrix.connector }}
186+
env:
187+
GCP_GSM_CREDENTIALS: ${{ secrets.GCP_GSM_CREDENTIALS }}
188+
POETRY_DYNAMIC_VERSIONING_BYPASS: "0.0.0"
189+
run: |
190+
poetry run airbyte-cdk connector test
193191
194-
# Update the test report status with results
195-
- name: Update Test Report Status
196-
if: always() && steps.no_changes.outputs.status != 'cancelled' && steps.evaluate_output.outcome == 'success'
192+
- name: Run FAST Standard Tests [Manifest-Only Connectors]
193+
if: env.CONNECTOR_LANGUAGE == 'manifest-only'
194+
working-directory: airbyte-python-cdk
197195
env:
198-
GH_TOKEN: ${{ secrets.GH_PAT_MAINTENANCE_OCTAVIA }}
196+
GCP_GSM_CREDENTIALS: ${{ secrets.GCP_GSM_CREDENTIALS }}
197+
POETRY_DYNAMIC_VERSIONING_BYPASS: "0.0.0"
199198
run: |
200-
HEAD_SHA="${{ github.event.pull_request.head.sha || github.sha }}"
201-
gh api \
202-
--method POST \
203-
-H "Accept: application/vnd.github+json" \
204-
-H "X-GitHub-Api-Version: 2022-11-28" \
205-
repos/${{ github.repository }}/statuses/$HEAD_SHA \
206-
-f state="${{ steps.evaluate_output.outputs.success == 'true' && 'success' || 'failure' }}" \
207-
-f target_url="${{ steps.evaluate_output.outputs.html_report_url }}" \
208-
-f description="Click Details to view the test report" \
209-
-f context="${{ matrix.connector }} Test Report"
210-
211-
# Create failure status if report generation failed
212-
- name: Create Report Generation Failed Status
213-
if: always() && steps.no_changes.outputs.status != 'cancelled' && steps.evaluate_output.outcome != 'success'
199+
poetry run airbyte-cdk connector test ${{ matrix.connector }}
200+
201+
- name: Container Build and Test [All Connectors]
202+
if: steps.no_changes.outputs.status != 'cancelled'
203+
working-directory: airbyte-python-cdk
214204
env:
215-
GH_TOKEN: ${{ secrets.GH_PAT_MAINTENANCE_OCTAVIA }}
205+
GCP_GSM_CREDENTIALS: ${{ secrets.GCP_GSM_CREDENTIALS }}
206+
POETRY_DYNAMIC_VERSIONING_BYPASS: "0.0.0"
216207
run: |
217-
HEAD_SHA="${{ github.event.pull_request.head.sha || github.sha }}"
218-
gh api \
219-
--method POST \
220-
-H "Accept: application/vnd.github+json" \
221-
-H "X-GitHub-Api-Version: 2022-11-28" \
222-
repos/${{ github.repository }}/statuses/$HEAD_SHA \
223-
-f state="failure" \
224-
-f description="Failed to run connector tests." \
225-
-f context="${{ matrix.connector }} Test Report"
226-
227-
# Upload the job output to the artifacts
228-
- name: Upload Job Output
229-
id: upload_job_output
230-
if: always() && steps.no_changes.outputs.status != 'cancelled'
231-
uses: actions/upload-artifact@v4
232-
with:
233-
name: ${{matrix.connector}}-job-output
234-
path: airbyte/airbyte-ci/connectors/pipelines/pipeline_reports
208+
poetry run airbyte-cdk image test ${{ matrix.connector }}

airbyte_cdk/cli/airbyte_cdk/_connector.py

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def connector_cli_group() -> None:
101101
pass
102102

103103

104-
@connector_cli_group.command()
104+
@connector_cli_group.command("test")
105105
@click.argument(
106106
"connector",
107107
required=False,
@@ -114,10 +114,18 @@ def connector_cli_group() -> None:
114114
default=False,
115115
help="Only collect tests, do not run them.",
116116
)
117-
def test(
117+
@click.option(
118+
"--pytest-arg",
119+
"pytest_args", # ← map --pytest-arg into pytest_args
120+
type=str,
121+
multiple=True,
122+
help="Additional argument(s) to pass to pytest. Can be specified multiple times.",
123+
)
124+
def connector_test(
118125
connector: str | Path | None = None,
119126
*,
120127
collect_only: bool = False,
128+
pytest_args: list[str] | None = None,
121129
) -> None:
122130
"""Run connector tests.
123131
@@ -130,19 +138,36 @@ def test(
130138
directory. If the current working directory is not a connector directory (e.g. starting
131139
with 'source-') and no connector name or path is provided, the process will fail.
132140
"""
141+
click.echo("Connector test command executed.")
142+
connector_name, connector_directory = resolve_connector_name_and_directory(connector)
143+
144+
pytest_args = pytest_args or []
145+
if collect_only:
146+
pytest_args.append("--collect-only")
147+
148+
run_connector_tests(
149+
connector_name=connector_name,
150+
connector_directory=connector_directory,
151+
extra_pytest_args=pytest_args,
152+
)
153+
154+
155+
def run_connector_tests(
156+
connector_name: str,
157+
connector_directory: Path,
158+
extra_pytest_args: list[str],
159+
) -> None:
133160
if pytest is None:
134161
raise ImportError(
135162
"pytest is not installed. Please install pytest to run the connector tests."
136163
)
137-
click.echo("Connector test command executed.")
138-
connector_name, connector_directory = resolve_connector_name_and_directory(connector)
139164

140165
connector_test_suite = create_connector_test_suite(
141166
connector_name=connector_name if not connector_directory else None,
142167
connector_directory=connector_directory,
143168
)
144169

145-
pytest_args: list[str] = []
170+
pytest_args: list[str] = ["-p", "airbyte_cdk.test.standard_tests.pytest_hooks"]
146171
if connector_directory:
147172
pytest_args.append(f"--rootdir={connector_directory}")
148173
os.chdir(str(connector_directory))
@@ -158,8 +183,8 @@ def test(
158183
test_file_path.parent.mkdir(parents=True, exist_ok=True)
159184
test_file_path.write_text(file_text)
160185

161-
if collect_only:
162-
pytest_args.append("--collect-only")
186+
if extra_pytest_args:
187+
pytest_args.extend(extra_pytest_args)
163188

164189
pytest_args.append(str(test_file_path))
165190

@@ -170,7 +195,6 @@ def test(
170195

171196
click.echo(f"Running tests from connector directory: {connector_directory}...")
172197
click.echo(f"Test file: {test_file_path}")
173-
click.echo(f"Collect only: {collect_only}")
174198
click.echo(f"Pytest args: {pytest_args}")
175199
click.echo("Invoking Pytest...")
176200
exit_code = pytest.main(

airbyte_cdk/cli/airbyte_cdk/_image.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import rich_click as click
1212

13+
from airbyte_cdk.cli.airbyte_cdk._connector import run_connector_tests
1314
from airbyte_cdk.models.connector_metadata import MetadataFile
1415
from airbyte_cdk.utils.connector_paths import resolve_connector_name_and_directory
1516
from airbyte_cdk.utils.docker import (
@@ -88,6 +89,81 @@ def build(
8889
sys.exit(1)
8990

9091

92+
@image_cli_group.command("test")
93+
@click.argument(
94+
"connector",
95+
required=False,
96+
type=str,
97+
metavar="[CONNECTOR]",
98+
)
99+
@click.option(
100+
"--image",
101+
help="Image to test, instead of building a new one.",
102+
)
103+
def image_test( # "image test" command
104+
connector: str | None = None,
105+
*,
106+
image: str | None = None,
107+
) -> None:
108+
"""Test a connector Docker image.
109+
110+
[CONNECTOR] can be a connector name (e.g. 'source-pokeapi'), a path to a connector directory, or omitted to use the current working directory.
111+
If a string containing '/' is provided, it is treated as a path. Otherwise, it is treated as a connector name.
112+
113+
If an image is provided, it will be used for testing instead of building a new one.
114+
115+
Note: You should run `airbyte-cdk secrets fetch` before running this command to ensure
116+
that the secrets are available for the connector tests.
117+
"""
118+
if not verify_docker_installation():
119+
click.echo(
120+
"Docker is not installed or not running. Please install Docker and try again.", err=True
121+
)
122+
sys.exit(1)
123+
124+
connector_name, connector_directory = resolve_connector_name_and_directory(connector)
125+
126+
# Select only tests with the 'image_tests' mark
127+
pytest_args = ["-m", "image_tests"]
128+
if not image:
129+
metadata_file_path: Path = connector_directory / "metadata.yaml"
130+
try:
131+
metadata = MetadataFile.from_file(metadata_file_path)
132+
except (FileNotFoundError, ValueError) as e:
133+
click.echo(
134+
f"Error loading metadata file '{metadata_file_path}': {e!s}",
135+
err=True,
136+
)
137+
sys.exit(1)
138+
139+
tag = "dev-latest"
140+
image = f"{metadata.data.dockerRepository}:{tag}"
141+
click.echo(f"Building Image for Connector: {image}")
142+
try:
143+
image = build_connector_image(
144+
connector_directory=connector_directory,
145+
connector_name=connector_name,
146+
metadata=metadata,
147+
tag=tag,
148+
no_verify=True,
149+
)
150+
except ConnectorImageBuildError as e:
151+
click.echo(
152+
f"Error building connector image: {e!s}",
153+
err=True,
154+
)
155+
sys.exit(1)
156+
157+
pytest_args.extend(["--connector-image", image])
158+
159+
click.echo(f"Testing Connector Image: {image}")
160+
run_connector_tests(
161+
connector_name=connector_name,
162+
connector_directory=connector_directory,
163+
extra_pytest_args=pytest_args,
164+
)
165+
166+
91167
__all__ = [
92168
"image_cli_group",
93169
]

0 commit comments

Comments
 (0)