Skip to content

Commit f264d37

Browse files
authored
Kotlin hackathon 1 (#7447)
* PHP Updates for Dockerfile and formatted output according to the new schema * Changes the testing flag in the Dockerfile * Removes output.txt from the repo * Added Kotlin Dockerfile changes and run_tests.py script
1 parent 94d6c40 commit f264d37

File tree

2 files changed

+298
-11
lines changed

2 files changed

+298
-11
lines changed

kotlin/Dockerfile

+24-11
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,31 @@
1-
# syntax=docker/dockerfile:1
2-
FROM gradle:latest
1+
# Use Gradle base image with JDK 21
2+
FROM gradle:8.5.0-jdk21
33

4-
# Update image
5-
RUN apt-get update && \
6-
apt-get upgrade -y && \
7-
apt-get clean && \
8-
rm -rf /var/lib/apt/lists/*
4+
# Install system dependencies
5+
RUN apt-get update -qq && \
6+
DEBIAN_FRONTEND=noninteractive apt-get install -qq -y \
7+
python3 \
8+
python3-pip \
9+
git \
10+
curl \
11+
vim \
12+
less \
13+
ca-certificates && \
14+
apt-get clean && rm -rf /var/lib/apt/lists/*
915

10-
# Copy source code
11-
COPY . /kotlin
16+
# Install Python dependencies
17+
RUN pip3 install --quiet --no-cache-dir boto3
18+
19+
# Set working directory
20+
WORKDIR /app
21+
22+
# Copy only the Python script (it clones code later)
23+
COPY run_tests.py .
1224

1325
# Set non-root user
1426
RUN useradd -m automation && \
15-
chown -R automation:automation /kotlin
27+
chown -R automation:automation /app
1628
USER automation:automation
1729

18-
CMD ["/kotlin/run_tests.sh"]
30+
# Default entrypoint (optional: run script automatically)
31+
CMD ["python3", "run_tests.py"]

kotlin/run_tests.py

+274
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
import os
2+
import subprocess
3+
import json
4+
import datetime
5+
import boto3
6+
import time
7+
import re
8+
from botocore.exceptions import ClientError
9+
10+
# DynamoDB setup
11+
# dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
12+
SUMMARY_TABLE = 'TestRunSummaries' # Table for test run summaries
13+
14+
def run_command(command, cwd=None):
15+
"""Run a shell command and return the returncode and output."""
16+
try:
17+
result = subprocess.run(command, cwd=cwd, check=True, text=True, capture_output=True)
18+
return result.returncode, result.stdout + result.stderr
19+
except subprocess.CalledProcessError as e:
20+
#print(f"Error running command: {e.cmd}\nReturn code: {e.returncode}\n{e.stdout or e.stderr}")
21+
return e.returncode, (e.stdout or '') + (e.stderr or '')
22+
23+
def parse_gradle_test_results(output):
24+
"""Parse Gradle test results and return counts and failure summaries."""
25+
passed = failed = skipped = 0
26+
failure_summary_lines = []
27+
28+
lines = output.splitlines()
29+
in_failure_block = False
30+
current_failure = ""
31+
32+
for line in lines:
33+
stripped = line.strip()
34+
35+
# Count test result types
36+
if re.match(r'.+ > .+ PASSED', stripped):
37+
passed += 1
38+
elif re.match(r'.+ > .+ FAILED', stripped):
39+
failed += 1
40+
in_failure_block = True
41+
failure_summary_lines.append(f"Failed: {stripped}")
42+
elif re.match(r'.+ > .+ SKIPPED', stripped):
43+
skipped += 1
44+
45+
# Capture stack trace or explanation for the failure
46+
if in_failure_block:
47+
if stripped == '' or stripped.startswith('BUILD') or re.match(r'^\d+\) ', stripped):
48+
in_failure_block = False
49+
else:
50+
failure_summary_lines.append(line)
51+
52+
# Capture the test class and method that failed
53+
match = re.match(r'(.+) \((.+)\)', stripped) # Match "TestClass (testMethod)"
54+
if match:
55+
failure_details = f"Test Failed: {match.group(1)} - Method: {match.group(2)}"
56+
if failure_details not in failure_summary_lines:
57+
failure_summary_lines.append(failure_details)
58+
59+
failure_summary = "\n".join(failure_summary_lines) if failure_summary_lines else "No failure details found."
60+
return passed, failed, skipped, failure_summary
61+
62+
def log_failure_to_dynamodb(service_name, service_path, failure_summary, run_id):
63+
"""Log failed test results to DynamoDB."""
64+
# client = boto3.client('dynamodb', region_name='us-east-1')
65+
table_name = 'TestFailures'
66+
try:
67+
pass
68+
# client.put_item(
69+
# TableName=table_name,
70+
# Item={
71+
# 'ServiceName': {'S': service_name},
72+
# 'ServicePath': {'S': service_path},
73+
# 'ErrorSummary': {'S': failure_summary},
74+
# 'RunId': {'S': run_id},
75+
# 'Timestamp': {'S': str(datetime.datetime.utcnow())}
76+
# }
77+
# )
78+
#print(f"🛑 Logged failure to DynamoDB: {service_name}")
79+
except ClientError as e:
80+
pass
81+
#print(f"❗ Failed to log failure: {e}")
82+
83+
def has_test_annotations(service_path):
84+
"""Check if the service contains any test files with @Test annotation."""
85+
test_dirs = [
86+
os.path.join(service_path, "src", "test", "kotlin"),
87+
os.path.join(service_path, "src", "test", "java")
88+
]
89+
for test_dir in test_dirs:
90+
for root, _, files in os.walk(test_dir):
91+
for file in files:
92+
if file.endswith(".kt") or file.endswith(".java"):
93+
full_path = os.path.join(root, file)
94+
try:
95+
with open(full_path, 'r', encoding='utf-8') as f:
96+
content = f.read()
97+
if "@Test" in content:
98+
return True
99+
except Exception as e:
100+
pass
101+
#print(f"⚠️ Could not read {full_path}: {e}")
102+
return False
103+
104+
def run_gradle_tests(service_path, run_id):
105+
"""Run Gradle tests for a given service if test files with @Test exist."""
106+
#print(f"\n🔧 Testing: {service_path}")
107+
108+
if not has_test_annotations(service_path):
109+
#print(f"⚠️ No test files with @Test found in {service_path}. Skipping.")
110+
return 0, 0, 0
111+
112+
gradlew = os.path.join(service_path, "gradlew")
113+
if os.path.exists(gradlew):
114+
gradle_command = "./gradlew"
115+
os.chmod(gradlew, 0o775)
116+
else:
117+
build_file_path = os.path.join(service_path, "build.gradle.kts")
118+
if not os.path.isfile(build_file_path):
119+
#print(f"❌ No Gradle build file found in {service_path}. Skipping test for this service.")
120+
return 0, 0, 0
121+
gradle_command = "gradle"
122+
123+
returncode, output = run_command([gradle_command, "test"], cwd=service_path)
124+
passed, failed, skipped, failure_summary = parse_gradle_test_results(output)
125+
status = "✅ Passed" if failed == 0 else "❌ Failed"
126+
#print(f"📊 Result: {status} — Passed: {passed}, Failed: {failed}, Skipped: {skipped}")
127+
128+
if failed > 0:
129+
service_name = os.path.basename(service_path)
130+
# log_failure_to_dynamodb(service_name, service_path, failure_summary, run_id)
131+
132+
return passed, failed, skipped
133+
134+
def get_service_folders(base_dir):
135+
"""Get a list of all service directories sorted alphabetically."""
136+
service_folders = []
137+
for root, dirs, files in os.walk(base_dir):
138+
for dir_name in dirs:
139+
gradle_file = os.path.join(root, dir_name, 'build.gradle.kts')
140+
if os.path.isfile(gradle_file):
141+
service_folders.append(os.path.join(root, dir_name))
142+
return sorted(service_folders)
143+
144+
def run_tests_for_all_services(base_dir, run_id):
145+
"""Run tests for all services in alphabetical order."""
146+
service_folders = get_service_folders(base_dir)
147+
passed_total = failed_total = skipped_total = 0
148+
services_tested = 0
149+
150+
current = 0
151+
limit = 1
152+
for service in service_folders:
153+
if current >= limit:
154+
break
155+
current += 1
156+
passed, failed, skipped = run_gradle_tests(service, run_id)
157+
passed_total += passed
158+
failed_total += failed
159+
skipped_total += skipped
160+
services_tested += 1
161+
162+
return passed_total, failed_total, skipped_total, services_tested
163+
164+
def log_test_run_summary(run_id, tested_services, total_passed, total_failed, total_skipped, elapsed_seconds):
165+
# summary_table = dynamodb.Table(SUMMARY_TABLE)
166+
timestamp = datetime.datetime.utcnow().isoformat()
167+
168+
item = {
169+
"RunId": run_id,
170+
"Timestamp": timestamp,
171+
"ServicesTested": tested_services,
172+
"TotalPassed": total_passed,
173+
"TotalFailed": total_failed,
174+
"TotalSkipped": total_skipped,
175+
"TotalTimeSeconds": int(elapsed_seconds),
176+
"Language": "Kotlin"
177+
}
178+
179+
try:
180+
pass
181+
# summary_table.put_item(Item=item)
182+
#print(f"🗃️ Test run summary logged to DynamoDB with RunId: {run_id}")
183+
except ClientError as e:
184+
pass
185+
#print(f"❗ Failed to log test summary: {e}")
186+
187+
def write_language_test_stats(language: str, total_tests: int, passed_tests: int):
188+
"""
189+
Write test statistics to the LanguageTestStats DynamoDB table.
190+
191+
Parameters:
192+
language (str): Programming language name (e.g., "Kotlin").
193+
total_tests (int): Total number of tests run.
194+
passed_tests (int): Number of tests that passed.
195+
"""
196+
pass_rate = (passed_tests / total_tests) * 100 if total_tests > 0 else 0
197+
198+
# dynamodb = boto3.client("dynamodb", "us-east-1")
199+
200+
item = {
201+
'Language': {'S': language},
202+
'TotalTests': {'N': str(total_tests)},
203+
'PassedTests': {'N': str(passed_tests)},
204+
'PassRate': {'N': str(round(pass_rate, 2))}
205+
}
206+
207+
try:
208+
pass
209+
# dynamodb.put_item(
210+
# TableName="LanguageTestStats",
211+
# Item=item
212+
# )
213+
#print(f"✅ Wrote stats for {language}: {passed_tests}/{total_tests} passed ({pass_rate:.2f}%)")
214+
except ClientError as e:
215+
pass
216+
#print(f"❌ Failed to write stats for {language} to DynamoDB: {e}")
217+
218+
def main():
219+
repo_url = "https://github.com/awsdocs/aws-doc-sdk-examples.git"
220+
clone_dir = "/app/aws-doc-sdk-examples"
221+
ROOT_TEST_DIR = "kotlin/services"
222+
start_time = time.time()
223+
224+
if not os.path.exists(clone_dir):
225+
#print(f"📥 Cloning repo: {repo_url}")
226+
returncode, output = run_command(["git", "clone", repo_url, clone_dir])
227+
if returncode != 0:
228+
#print("❌ Failed to clone repository.")
229+
return
230+
231+
base_dir = os.path.join(clone_dir, ROOT_TEST_DIR)
232+
run_id = str(int(time.time()))
233+
passed_total, failed_total, skipped_total, services_tested = run_tests_for_all_services(base_dir, run_id)
234+
235+
elapsed = time.time() - start_time
236+
# #print("\n===== ✅ Final Test Summary =====")
237+
#print(f"Services Tested: {services_tested}", file=sys.stderr)
238+
#print(f"Total Tests Passed: {passed_total}")
239+
#print(f"Total Tests Failed: {failed_total}")
240+
#print(f"Total Tests Skipped: {skipped_total}")
241+
#print(f"Total Time: {int(elapsed // 60)} min {int(elapsed % 60)} sec")
242+
print(
243+
{
244+
"schema-version": "0.0.1",
245+
"results": {
246+
"tool": "gradle",
247+
"summary": {
248+
"tests": services_tested,
249+
"passed": passed_total,
250+
"failed": failed_total,
251+
"skipped": skipped_total,
252+
"start_time": 0,
253+
"stop_time": int(elapsed)
254+
},
255+
"tests": [
256+
{
257+
"name": "test 1",
258+
"status": "failed",
259+
"duration": int(elapsed),
260+
"message": "apigateway",
261+
"log": "probably something to do with credentials"
262+
}
263+
]
264+
}
265+
})
266+
267+
# 🪪 Log to DynamoDB at the END of all testing
268+
# log_test_run_summary(run_id, services_tested, passed_total, failed_total, skipped_total, elapsed)
269+
270+
total = passed_total + failed_total
271+
write_language_test_stats("Kotlin", total, passed_total)
272+
273+
if __name__ == "__main__":
274+
main()

0 commit comments

Comments
 (0)