Skip to content

Commit f6a6250

Browse files
authored
Merge branch 'main' into josh/voice
2 parents a6a6b9b + 532c107 commit f6a6250

File tree

5 files changed

+177
-19
lines changed

5 files changed

+177
-19
lines changed

.github/workflows/integration_tests.yml

Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
name: uipath - Integration Tests
22

33
on:
4-
push:
5-
branches: [ main, develop ]
64
pull_request:
75
branches: [ main ]
86

@@ -12,30 +10,71 @@ permissions:
1210
actions: read
1311

1412
jobs:
13+
detect-changed-packages:
14+
runs-on: ubuntu-latest
15+
outputs:
16+
packages: ${{ steps.detect.outputs.packages }}
17+
count: ${{ steps.detect.outputs.count }}
18+
steps:
19+
- name: Checkout
20+
uses: actions/checkout@v4
21+
with:
22+
fetch-depth: 0
23+
24+
- name: Setup Python
25+
uses: actions/setup-python@v5
26+
with:
27+
python-version: '3.11'
28+
29+
- name: Detect changed packages
30+
id: detect
31+
env:
32+
GITHUB_EVENT_NAME: ${{ github.event_name }}
33+
BASE_SHA: ${{ github.event.pull_request.base.sha }}
34+
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
35+
run: python .github/scripts/detect_changed_packages.py
36+
1537
discover-testcases:
38+
needs: [detect-changed-packages]
39+
if: needs.detect-changed-packages.outputs.count > 0
1640
runs-on: ubuntu-latest
1741
outputs:
1842
testcases: ${{ steps.discover.outputs.testcases }}
43+
count: ${{ steps.discover.outputs.count }}
1944
steps:
2045
- name: Checkout code
2146
uses: actions/checkout@v4
2247

2348
- name: Discover testcases
2449
id: discover
25-
working-directory: packages/uipath
50+
env:
51+
PACKAGES: ${{ needs.detect-changed-packages.outputs.packages }}
2652
run: |
27-
# Find all testcase folders (excluding common folders like README, etc.)
28-
testcase_dirs=$(find testcases -maxdepth 1 -type d -name "*-*" | sed 's|testcases/||' | sort)
29-
30-
echo "Found testcase directories:"
31-
echo "$testcase_dirs"
53+
# Discover testcases from all affected packages
54+
all_testcases="[]"
55+
56+
for package in $(echo "$PACKAGES" | jq -r '.[]'); do
57+
testcases_dir="packages/$package/testcases"
58+
if [ -d "$testcases_dir" ]; then
59+
testcase_dirs=$(find "$testcases_dir" -maxdepth 1 -type d -name "*-*" | sed "s|$testcases_dir/||" | sort)
60+
if [ -n "$testcase_dirs" ]; then
61+
echo "Found testcases in $package:"
62+
echo "$testcase_dirs"
63+
# Add as package/testcase entries
64+
package_testcases=$(echo "$testcase_dirs" | jq -R -c --arg pkg "$package" '{package: $pkg, testcase: .}')
65+
all_testcases=$(echo "$all_testcases" | jq -c --argjson new "[$( echo "$package_testcases" | paste -sd, )]" '. + $new')
66+
fi
67+
fi
68+
done
3269
33-
# Convert to JSON array for matrix
34-
testcases_json=$(echo "$testcase_dirs" | jq -R -s -c 'split("\n")[:-1]')
35-
echo "testcases=$testcases_json" >> $GITHUB_OUTPUT
70+
echo "All testcases: $all_testcases"
71+
count=$(echo "$all_testcases" | jq 'length')
72+
echo "testcases=$all_testcases" >> $GITHUB_OUTPUT
73+
echo "count=$count" >> $GITHUB_OUTPUT
3674
3775
integration-tests:
3876
needs: [discover-testcases]
77+
if: needs.discover-testcases.outputs.count > 0
3978
runs-on: ubuntu-latest
4079
container:
4180
image: ghcr.io/astral-sh/uv:python3.12-bookworm
@@ -48,14 +87,14 @@ jobs:
4887
testcase: ${{ fromJson(needs.discover-testcases.outputs.testcases) }}
4988
environment: [alpha, cloud, staging]
5089

51-
name: "${{ matrix.testcase }} / ${{ matrix.environment }}"
90+
name: "${{ matrix.testcase.testcase }} / ${{ matrix.environment }}"
5291

5392
steps:
5493
- name: Checkout code
5594
uses: actions/checkout@v4
5695

5796
- name: Install dependencies
58-
working-directory: packages/uipath
97+
working-directory: packages/${{ matrix.testcase.package }}
5998
run: uv sync
6099

61100
- name: Run testcase
@@ -70,12 +109,13 @@ jobs:
70109
TELEMETRY_CONNECTION_STRING: ${{ secrets.APPLICATIONINSIGHTS_CONNECTION_STRING }}
71110
APP_INSIGHTS_APP_ID: ${{ secrets.APP_INSIGHTS_APP_ID }}
72111
APP_INSIGHTS_API_KEY: ${{ secrets.APP_INSIGHTS_API_KEY }}
73-
working-directory: packages/uipath/testcases/${{ matrix.testcase }}
112+
working-directory: packages/${{ matrix.testcase.package }}/testcases/${{ matrix.testcase.testcase }}
74113
run: |
75114
# If any errors occur execution will stop with exit code
76115
set -e
77116
78-
echo "Running testcase: ${{ matrix.testcase }}"
117+
echo "Running testcase: ${{ matrix.testcase.testcase }}"
118+
echo "Package: ${{ matrix.testcase.package }}"
79119
echo "Environment: ${{ matrix.environment }}"
80120
echo "Working directory: $(pwd)"
81121
@@ -84,12 +124,20 @@ jobs:
84124
bash ../common/validate_output.sh
85125
86126
summarize-results:
87-
needs: [integration-tests]
127+
needs: [detect-changed-packages, discover-testcases, integration-tests]
88128
runs-on: ubuntu-latest
89-
if: always() # This ensures the job runs even if the tests fail
129+
if: always()
90130
steps:
91131
- name: Check integration tests status
92132
run: |
133+
if [[ "${{ needs.detect-changed-packages.outputs.count }}" == "0" ]]; then
134+
echo "No packages changed - skipping integration tests"
135+
exit 0
136+
fi
137+
if [[ "${{ needs.discover-testcases.outputs.count }}" == "0" ]]; then
138+
echo "No testcases found for changed packages - skipping"
139+
exit 0
140+
fi
93141
if [[ "${{ needs.integration-tests.result }}" == "success" ]]; then
94142
echo "All integration tests passed"
95143
else

packages/uipath-core/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath-core"
3-
version = "0.5.5"
3+
version = "0.5.6"
44
description = "UiPath Core abstractions"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

packages/uipath-core/src/uipath/core/guardrails/_evaluators.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,9 @@ def evaluate_word_rule(
195195
) -> tuple[bool, str]:
196196
"""Evaluate a word rule against input and output data."""
197197
fields = get_fields_from_selector(rule.field_selector, input_data, output_data)
198+
if not fields:
199+
return True, "No fields to validate"
200+
198201
operator = _humanize_guardrail_func(rule.detects_violation) or "violation check"
199202
field_paths = ", ".join({field_ref.path for _, field_ref in fields})
200203

@@ -237,6 +240,9 @@ def evaluate_number_rule(
237240
) -> tuple[bool, str]:
238241
"""Evaluate a number rule against input and output data."""
239242
fields = get_fields_from_selector(rule.field_selector, input_data, output_data)
243+
if not fields:
244+
return True, "No fields to validate"
245+
240246
operator = _humanize_guardrail_func(rule.detects_violation) or "violation check"
241247
field_paths = ", ".join({field_ref.path for _, field_ref in fields})
242248
for field_value, field_ref in fields:
@@ -281,6 +287,9 @@ def evaluate_boolean_rule(
281287
) -> tuple[bool, str]:
282288
"""Evaluate a boolean rule against input and output data."""
283289
fields = get_fields_from_selector(rule.field_selector, input_data, output_data)
290+
if not fields:
291+
return True, "No fields to validate"
292+
284293
operator = _humanize_guardrail_func(rule.detects_violation) or "violation check"
285294
field_paths = ", ".join({field_ref.path for _, field_ref in fields})
286295
for field_value, field_ref in fields:

packages/uipath-core/tests/guardrails/test_deterministic_guardrails_service.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1466,3 +1466,104 @@ def _create_guardrail_with_always_rule(
14661466
),
14671467
],
14681468
)
1469+
1470+
1471+
class TestMissingFieldPassesValidation:
1472+
"""Test that rules referencing missing fields pass validation."""
1473+
1474+
def test_word_rule_missing_field_passes(
1475+
self, service: DeterministicGuardrailsService
1476+
) -> None:
1477+
guardrail = DeterministicGuardrail(
1478+
id="test-missing-field",
1479+
name="Missing Field Guardrail",
1480+
description="Test missing field",
1481+
enabled_for_evals=True,
1482+
guardrail_type="custom",
1483+
selector=GuardrailSelector(
1484+
scopes=[GuardrailScope.TOOL], match_names=["test"]
1485+
),
1486+
rules=[
1487+
WordRule(
1488+
rule_type="word",
1489+
field_selector=SpecificFieldsSelector(
1490+
selector_type="specific",
1491+
fields=[
1492+
FieldReference(path="sentence2", source=FieldSource.INPUT)
1493+
],
1494+
),
1495+
detects_violation=lambda s: len(s or "") > 0,
1496+
rule_description="sentence2 is not empty",
1497+
),
1498+
],
1499+
)
1500+
result = service._evaluate_deterministic_guardrail(
1501+
input_data={"sentence1": "hello"},
1502+
output_data={},
1503+
guardrail=guardrail,
1504+
)
1505+
assert result.result == GuardrailValidationResultType.PASSED
1506+
1507+
def test_number_rule_missing_field_passes(
1508+
self, service: DeterministicGuardrailsService
1509+
) -> None:
1510+
guardrail = DeterministicGuardrail(
1511+
id="test-missing-field",
1512+
name="Missing Field Guardrail",
1513+
description="Test missing field",
1514+
enabled_for_evals=True,
1515+
guardrail_type="custom",
1516+
selector=GuardrailSelector(
1517+
scopes=[GuardrailScope.TOOL], match_names=["test"]
1518+
),
1519+
rules=[
1520+
NumberRule(
1521+
rule_type="number",
1522+
field_selector=SpecificFieldsSelector(
1523+
selector_type="specific",
1524+
fields=[FieldReference(path="age", source=FieldSource.INPUT)],
1525+
),
1526+
detects_violation=lambda n: n is not None and n < 0,
1527+
rule_description="age is negative",
1528+
),
1529+
],
1530+
)
1531+
result = service._evaluate_deterministic_guardrail(
1532+
input_data={"name": "test"},
1533+
output_data={},
1534+
guardrail=guardrail,
1535+
)
1536+
assert result.result == GuardrailValidationResultType.PASSED
1537+
1538+
def test_boolean_rule_missing_field_passes(
1539+
self, service: DeterministicGuardrailsService
1540+
) -> None:
1541+
guardrail = DeterministicGuardrail(
1542+
id="test-missing-field",
1543+
name="Missing Field Guardrail",
1544+
description="Test missing field",
1545+
enabled_for_evals=True,
1546+
guardrail_type="custom",
1547+
selector=GuardrailSelector(
1548+
scopes=[GuardrailScope.TOOL], match_names=["test"]
1549+
),
1550+
rules=[
1551+
BooleanRule(
1552+
rule_type="boolean",
1553+
field_selector=SpecificFieldsSelector(
1554+
selector_type="specific",
1555+
fields=[
1556+
FieldReference(path="is_active", source=FieldSource.INPUT)
1557+
],
1558+
),
1559+
detects_violation=lambda b: b is False,
1560+
rule_description="is_active is false",
1561+
),
1562+
],
1563+
)
1564+
result = service._evaluate_deterministic_guardrail(
1565+
input_data={"name": "test"},
1566+
output_data={},
1567+
guardrail=guardrail,
1568+
)
1569+
assert result.result == GuardrailValidationResultType.PASSED

packages/uipath-core/uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)