From f5f2bfea7d0d9309ada8e927aca412e5d174e2d6 Mon Sep 17 00:00:00 2001 From: Himanshu Kumar Sharma <97340980+HeeManSu@users.noreply.github.com> Date: Thu, 20 Jun 2024 21:11:17 +0530 Subject: [PATCH] Test issues (#52) * test deletion issue and infinite loop issue fixed * added formatter for shell scripting * Added test for dependencies * dependecy test removed from ci * redeloy removed from ci * Deletion test fixed * startup autodeploy added * condtion corrected * dependency issue solved --- .vscode/settings.json | 11 +- docker-compose.yml | 1 + src/utils/install.ts | 75 ++++++++++-- test/data/python-dependency-app/handler.py | 19 +++ test/data/python-dependency-app/metacall.json | 5 + .../python-dependency-app/requirements.txt | 1 + test/test.sh | 115 ++++++++++++------ 7 files changed, 176 insertions(+), 51 deletions(-) create mode 100644 test/data/python-dependency-app/handler.py create mode 100644 test/data/python-dependency-app/metacall.json create mode 100644 test/data/python-dependency-app/requirements.txt diff --git a/.vscode/settings.json b/.vscode/settings.json index e9c6df6..b26fec4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,13 +5,14 @@ "source.organizeImports": "explicit", "source.fixAll": "explicit" }, - "editor.rulers": [ - 80 - ], + "editor.rulers": [80], "editor.quickSuggestions": { "comments": "on", "strings": "on", "other": "on" }, - "editor.suggestOnTriggerCharacters": true -} \ No newline at end of file + "editor.suggestOnTriggerCharacters": true, + "[shellscript]": { + "editor.defaultFormatter": "foxundermoon.shell-format" + } +} diff --git a/docker-compose.yml b/docker-compose.yml index 2b77dc5..0016827 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -39,6 +39,7 @@ services: target: test environment: TEST_FAAS_STARTUP_DEPLOY: ${TEST_FAAS_STARTUP_DEPLOY:-false} + TEST_FAAS_DEPENDENCY_DEPLOY: ${TEST_FAAS_DEPENDENCY_DEPLOY:-true} network_mode: host depends_on: - faas diff --git a/src/utils/install.ts b/src/utils/install.ts index 4d74827..f5d3e1d 100644 --- a/src/utils/install.ts +++ b/src/utils/install.ts @@ -1,28 +1,81 @@ +import { promises as fs } from 'fs'; +import path from 'path'; import { Resource } from '../app'; import { exec } from './exec'; -const createInstallDependenciesScript = ( - runner: string, - path: string -): string => { +type Runner = 'python' | 'nodejs' | 'ruby' | 'csharp'; + +const targetFiles: Record = { + nodejs: 'package.json', + python: 'requirements.txt', + ruby: 'Gemfile', + csharp: 'project.json' +}; + +const isRunner = (runner: string): runner is Runner => { + return ['nodejs', 'python', 'ruby', 'csharp'].includes(runner); +}; + +const findDependencyFile = async ( + dir: string, + runner: Runner +): Promise => { + const files = await fs.readdir(dir); + + for (const file of files) { + const fullPath = path.join(dir, file); + const stat = await fs.stat(fullPath); + + if (stat.isDirectory()) { + const result = await findDependencyFile(fullPath, runner); + if (result) return result; + } else if (file === targetFiles[runner]) { + return dir; + } + } + + return null; +}; + +const createInstallDependenciesScript = async ( + runner: Runner, + basePath: string +): Promise => { + const dependencyFilePath = await findDependencyFile(basePath, runner); + + if (!dependencyFilePath) { + throw new Error(`No ${runner} dependencies file found`); + } + const installDependenciesScript: Record = { - python: `cd ${path} && metacall pip3 install -r requirements.txt`, - nodejs: `cd ${path} && metacall npm i`, - ruby: `cd ${path} && metacall bundle install`, - csharp: `cd ${path} && metacall dotnet restore && metacall dotnet release` + python: `cd ${dependencyFilePath} && metacall pip3 install -r requirements.txt`, + nodejs: `cd ${dependencyFilePath} && metacall npm i`, + ruby: `cd ${dependencyFilePath} && metacall bundle install`, + csharp: `cd ${dependencyFilePath} && metacall dotnet restore && metacall dotnet release` }; return installDependenciesScript[runner]; }; +// Todo: Async Error Handling export const installDependencies = async ( resource: Resource ): Promise => { if (!resource.runners) return; for (const runner of resource.runners) { - if (runner == undefined) continue; - else { - await exec(createInstallDependenciesScript(runner, resource.path)); + if (runner && isRunner(runner)) { + try { + const script = await createInstallDependenciesScript( + runner, + resource.path + ); + await exec(script); + } catch (err) { + console.error( + `Failed to install dependencies for runner ${runner}:`, + err + ); + } } } }; diff --git a/test/data/python-dependency-app/handler.py b/test/data/python-dependency-app/handler.py new file mode 100644 index 0000000..d8ca3d6 --- /dev/null +++ b/test/data/python-dependency-app/handler.py @@ -0,0 +1,19 @@ +import requests + +# Fetch random joke function +def fetchJoke(): + try: + response = requests.get('https://official-joke-api.appspot.com/random_joke') + response.raise_for_status() # Raise an error for bad status codes + joke = response.json() + return { + 'setup': joke['setup'], + 'punchline': joke['punchline'] + } + except requests.RequestException as e: + return {'message': 'Error fetching joke', 'error': str(e)} + +# Example usage +if __name__ == "__main__": + joke = fetchJoke() + print(joke) diff --git a/test/data/python-dependency-app/metacall.json b/test/data/python-dependency-app/metacall.json new file mode 100644 index 0000000..957b945 --- /dev/null +++ b/test/data/python-dependency-app/metacall.json @@ -0,0 +1,5 @@ +{ + "language_id": "py", + "path": ".", + "scripts": ["handler.py"] +} diff --git a/test/data/python-dependency-app/requirements.txt b/test/data/python-dependency-app/requirements.txt new file mode 100644 index 0000000..f229360 --- /dev/null +++ b/test/data/python-dependency-app/requirements.txt @@ -0,0 +1 @@ +requests diff --git a/test/test.sh b/test/test.sh index a94d93e..1658441 100755 --- a/test/test.sh +++ b/test/test.sh @@ -21,9 +21,20 @@ set -exuo pipefail +# Maximum number of retries +MAX_RETRIES=5 +RETRY_COUNT=0 + # FaaS base URL BASE_URL="http://localhost:9000" +# #function to check readiness +function check_readiness() { + local status_code + status_code=$(curl -s -o /dev/null -w "%{http_code}" $BASE_URL/readiness) + echo "$status_code" +} + # Get the prefix of a deployment function getPrefix() { prefix=$(metacall-deploy --dev --inspect Raw | jq -r ".[] | select(.suffix == \"$1\") | .prefix") @@ -38,56 +49,90 @@ function deploy() { } # Wait for the FaaS to be ready -while [[ ! $(curl -s -o /dev/null -w "%{http_code}" $BASE_URL/readiness) = "200" ]]; do +while [[ $(check_readiness) != "200" ]]; do + if [[ $RETRY_COUNT -ge $MAX_RETRIES ]]; then + echo "Readiness check failed after $MAX_RETRIES retries." + exit 1 + fi + RETRY_COUNT=$((RETRY_COUNT + 1)) sleep 1 done echo "FaaS ready, starting tests." -# Test deploy (Python) without dependencies -app="python-base-app" -pushd data/$app +# Function to run tests +function run_tests() { + local app=$1 + local test_func=$2 + + pushd data/$app deploy prefix=$(getPrefix $app) url=$BASE_URL/$prefix/$app/v1/call - [[ $(curl -s $url/number) = 100 ]] || exit 1 - [[ $(curl -s $url/text) = '"asd"' ]] || exit 1 -popd - -# Test inspect -echo "Testing inspect functionality." + $test_func $url + popd -# Inspect the deployed projects -inspect_response=$(curl -s $BASE_URL/api/inspect) + # Test inspect + echo "Testing inspect functionality." -# Verify inspection -if [[ $inspect_response != *"$prefix"* ]]; then - echo "Inspection test failed." - exit 1 -fi + # Inspect the deployed projects + inspect_response=$(curl -s $BASE_URL/api/inspect) -# Verify packages are included in the response -if [[ $inspect_response != *"packages"* ]]; then - echo "packages not found in inspection response." - exit 1 -fi + # Verify inspection + if [[ $inspect_response != *"$prefix"* ]]; then + echo "Inspection test failed." + exit 1 + fi -echo "Inspection test passed." + # Verify packages are included in the response + if [[ $inspect_response != *"packages"* ]]; then + echo "packages not found in inspection response." + exit 1 + fi -# Test delete only if we are not testing startup deployments -if [[ "${TEST_FAAS_STARTUP_DEPLOY}" == "true" ]]; then - echo "Testing delete functionality." + echo "Inspection test passed." + + # Test delete only if we are not testing startup deployments + if [[ "${TEST_FAAS_STARTUP_DEPLOY}" == "true" ]]; then + echo "Testing delete functionality." + + # Delete the deployed project + curl -X POST -H "Content-Type: application/json" -d '{"suffix":"'"$app"'","prefix":"'"$prefix"'","version":"v1"}' $BASE_URL/api/deploy/delete + + # Verify deletion + if [[ "$app" == "python-dependency-app" ]]; then + if [[ $(curl -s -o /dev/null -w "%{http_code}" $BASE_URL/$prefix/$app/v1/call/fetchJoke) != "404" ]]; then + echo "Deletion test failed." + exit 1 + fi + else + if [[ $(curl -s -o /dev/null -w "%{http_code}" $BASE_URL/$prefix/$app/v1/call/number) != "404" ]]; then + echo "Deletion test failed." + exit 1 + fi + fi + + echo "Deletion test passed." + fi +} - # Delete the deployed project - curl -X POST -H "Content-Type: application/json" -d '{"suffix":"python-base-app","prefix":"'"$prefix"'","version":"v1"}' $BASE_URL/api/deploy/delete +# Test function for python-base-app +function test_python_base_app() { + local url=$1 + [[ $(curl -s $url/number) = 100 ]] || exit 1 + [[ $(curl -s $url/text) = '"asd"' ]] || exit 1 +} - # Verify deletion - if [[ $(curl -s -o /dev/null -w "%{http_code}" $BASE_URL/$prefix/$app/v1/call/number) != "404" ]]; then - echo "Deletion test failed." - exit 1 - fi +# Test function for python-dependency-app +function test_python_dependency_app() { + local url=$1 + [[ $(curl -s $url/fetchJoke) == *"setup"* && $(curl -s $url/fetchJoke) == *"punchline"* ]] || exit 1 +} - echo "Deletion test passed." +# Run tests without dependencies +run_tests "python-base-app" test_python_base_app +if [[ "${TEST_FAAS_DEPENDENCY_DEPLOY}" == "true" ]]; then + run_tests "python-dependency-app" test_python_dependency_app fi -echo "Integration tests passed without errors." \ No newline at end of file +echo "Integration tests passed without errors."