diff --git a/.gitmodules b/.gitmodules index b7efcb4..e69de29 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +0,0 @@ -[submodule "test/bats"] - path = test/bats - url = https://github.com/bats-core/bats-core.git -[submodule "test/test_helper/bats-support"] - path = test/test_helper/bats-support - url = https://github.com/bats-core/bats-support.git -[submodule "test/test_helper/bats-assert"] - path = test/test_helper/bats-assert - url = https://github.com/bats-core/bats-assert.git diff --git a/test/README.md b/test/README.md deleted file mode 100644 index 2f9bc09..0000000 --- a/test/README.md +++ /dev/null @@ -1,104 +0,0 @@ -# Test Directory - -This directory is used for testing wild-cloud functionality using the Bats testing framework. - -## Contents -- `test_helper.bash` - Shared Bats test setup and utilities -- `test_helper/` - Bats framework extensions (bats-support, bats-assert) -- `bats/` - Bats core framework (git submodule) -- `fixtures/` - Test data and sample configuration files -- `*.bats` - Bats test files for different components -- `run_bats_tests.sh` - Runs the complete Bats test suite -- `tmp/` - Temporary test projects (auto-created/cleaned) - -## Test Files - -### `test_common_functions.bats` -Tests the core functions in `wild-common.sh`: -- `find_wc_home()` - Project root detection -- `init_wild_env()` - Environment setup -- `check_wild_directory()` - Project validation -- Print functions and utilities - -### `test_project_detection.bats` -Tests project detection and script execution: -- Script execution from various directory levels -- Environment variable setup from different paths -- Proper failure outside project directories - -### `test_config_functions.bats` -Tests configuration and secret access: -- `wild-config` command -- `wild-secret` command -- Configuration access from subdirectories -- Fixture data usage - -## Running Tests - -```bash -# Initialize git submodules (first time only) -git submodule update --init --recursive - -# Run all Bats tests -./run_bats_tests.sh - -# Run individual test files -./bats/bin/bats test_common_functions.bats -./bats/bin/bats test_project_detection.bats -./bats/bin/bats test_config_functions.bats - -# Test from subdirectory (should work) -cd deep/nested/path -../../../bin/wild-cluster-node-up --help -``` - -## Fixtures - -The `fixtures/` directory contains: -- `sample-config.yaml` - Complete test configuration -- `sample-secrets.yaml` - Test secrets file - -## Adding New Tests - -1. Create `test_.bats` following the Bats pattern: - ```bash - #!/usr/bin/env bats - - load 'test_helper' - - setup() { - setup_test_project "feature-test" - } - - teardown() { - teardown_test_project "feature-test" - } - - @test "feature description" { - # Your test here using Bats assertions - run some_command - assert_success - assert_output "expected output" - } - ``` - -2. Add test data to `fixtures/` if needed - -3. The Bats runner will automatically discover and run new tests - -## Common Test Functions - -From `test_helper.bash`: -- `setup_test_project "name"` - Creates test project in `tmp/` -- `teardown_test_project "name"` - Removes test project -- `create_test_project "name" [with-config]` - Creates additional test projects -- `remove_test_project "name"` - Removes additional test projects - -## Bats Assertions - -Available through bats-assert: -- `assert_success` / `assert_failure` - Check command exit status -- `assert_output "text"` - Check exact output -- `assert_output --partial "text"` - Check output contains text -- `assert_equal "$actual" "$expected"` - Check equality -- `assert [ condition ]` - General assertions \ No newline at end of file diff --git a/test/bats b/test/bats deleted file mode 160000 index 855844b..0000000 --- a/test/bats +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 855844b8344e67d60dc0f43fa39817ed7787f141 diff --git a/test/fixtures/sample-config.yaml b/test/fixtures/sample-config.yaml deleted file mode 100644 index 7aacfd0..0000000 --- a/test/fixtures/sample-config.yaml +++ /dev/null @@ -1,72 +0,0 @@ -wildcloud: - root: /test/path/wild-cloud -operator: - email: test@example.com -cloud: - domain: test.example.com - internalDomain: internal.test.example.com - dockerRegistryHost: docker-registry.internal.test.example.com - tz: America/New_York - router: - dynamicDns: test.ddns.com - ip: 192.168.100.1 - nfs: - host: test-nfs - mediaPath: /data/media - storageCapacity: 100Gi - dns: - ip: 192.168.100.50 - externalResolver: 8.8.8.8 - dhcpRange: 192.168.100.100,192.168.100.200 - dnsmasq: - interface: eth0 - username: testuser -cluster: - name: test-cluster - ipAddressPool: 192.168.100.240-192.168.100.249 - loadBalancerIp: 192.168.100.240 - kubernetes: - config: /home/testuser/.kube/config - context: default - dashboard: - adminUsername: admin - certManager: - namespace: cert-manager - cloudflare: - domain: example.com - ownerId: test-cluster-owner - externalDns: - ownerId: test-cluster-owner - dockerRegistry: - storage: 10Gi - nodes: - talos: - version: v1.10.4 - schematicId: test123456789abcdef - control: - vip: 192.168.100.200 - active: - 192.168.100.201: - maintenanceIp: 192.168.100.131 - interface: eth0 - disk: /dev/sda - control: "true" - 192.168.100.202: - interface: eth0 - disk: /dev/nvme0n1 - control: "true" - 192.168.100.210: - interface: eth0 - disk: /dev/sda - control: "false" -apps: - postgres: - database: postgres - user: postgres - storage: 10Gi - image: pgvector/pgvector:pg15 - timezone: America/New_York - redis: - image: redis:alpine - timezone: UTC - port: 6379 \ No newline at end of file diff --git a/test/fixtures/sample-secrets.yaml b/test/fixtures/sample-secrets.yaml deleted file mode 100644 index 9d4ebd3..0000000 --- a/test/fixtures/sample-secrets.yaml +++ /dev/null @@ -1,14 +0,0 @@ -operator: - cloudflareApiToken: test_api_token_123456789 -cluster: - dashboard: - adminPassword: test_admin_password_123 - certManager: - cloudflare: - apiToken: test_cf_token_456789 -apps: - postgres: - password: test_postgres_password_789 - databases: - immich: - password: test_immich_db_password_321 \ No newline at end of file diff --git a/test/run_bats_tests.sh b/test/run_bats_tests.sh deleted file mode 100755 index 1c4daac..0000000 --- a/test/run_bats_tests.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash - -# run_bats_tests.sh -# Run all Bats tests in the test directory - -set -e - -# Get the directory where this script is located -TEST_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -# Check if bats is available -if [ ! -f "$TEST_DIR/bats/bin/bats" ]; then - echo "Error: Bats not found. Make sure git submodules are initialized:" - echo " git submodule update --init --recursive" - exit 1 -fi - -echo "Running Wild Cloud Bats Test Suite..." -echo "======================================" - -# Run all .bats files -"$TEST_DIR/bats/bin/bats" "$TEST_DIR"/*.bats - -echo "" -echo "All tests completed!" \ No newline at end of file diff --git a/test/test-cloud/.gitignore b/test/test-cloud/.gitignore deleted file mode 100644 index f54d81a..0000000 --- a/test/test-cloud/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -.wildcloud -secrets.yaml -.bots/*/sessions -backup/ -.working -setup/cluster-nodes/generated/talosconfig diff --git a/test/test-cloud/README.md b/test/test-cloud/README.md deleted file mode 100644 index 11a19bf..0000000 --- a/test/test-cloud/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Test Wild Cloud Environment - -This directory is a test Wild Cloud home for debugging scripts and commands. - -## Usage - -```bash -cd test/test-cloud -wild-app-fetch -wild-app-add -wild-app-deploy -# etc. -``` - -## Files - -- `config.yaml` - Test configuration values -- `secrets.yaml` - Test secrets (safe to modify) -- `apps/` - Added apps for testing -- `.wildcloud/cache/` - Cached app definitions - -This environment is isolated and safe for testing Wild Cloud functionality. diff --git a/test/test-cloud/apps/README.md b/test/test-cloud/apps/README.md deleted file mode 100644 index 89592d8..0000000 --- a/test/test-cloud/apps/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# My Wild Cloud Apps - -This directory contains the definitions for _your_ wild-cloud apps. You can change them however you like. You should keep them all in git and make commits anytime you change something. Some `wild` commands will overwrite files in your app directory (like when you are updating apps, or updating your configuration) so you'll want to review any changes made to your files after using them using `git`. - -## Usage - -To list all available apps: - -```bash -wild-cloud-app-list -``` - -### App Workflow - -The Wild Cloud app workflow consists of three steps: - -1. **Fetch** - Download raw app templates to cache -2. **Config** - Apply your local configuration to templates -3. **Deploy** - Deploy configured app to Kubernetes - -### Commands - -To fetch an app template to cache: - -```bash -wild-app-fetch -``` - -To apply your configuration to a cached app (automatically fetches if not cached): - -```bash -wild-app-config -``` - -To deploy a configured app to Kubernetes: - -```bash -wild-app-deploy -``` - -### Quick Setup - -For a complete app setup and deployment: - -```bash -wild-app-config # Fetches if needed, then configures -wild-app-deploy # Deploys to Kubernetes -``` diff --git a/test/test-cloud/env.sh b/test/test-cloud/env.sh deleted file mode 100644 index f03054f..0000000 --- a/test/test-cloud/env.sh +++ /dev/null @@ -1,78 +0,0 @@ -#!/bin/bash - -# Set the WC_HOME environment variable to this script's directory. -# This variable is used consistently across the Wild Config scripts. -export WC_HOME="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)" - -# Add bin to path first so wild-config is available -export PATH="$WC_HOME/bin:$PATH" - -# Install kubectl -if ! command -v kubectl &> /dev/null; then - echo "Installing kubectl" - curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" - curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl.sha256" - echo "$(cat kubectl.sha256) kubectl" | sha256sum --check - sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl - rm kubectl kubectl.sha256 - echo "kubectl installed successfully." -fi - -# Install talosctl -if ! command -v talosctl &> /dev/null; then - echo "Installing talosctl" - curl -sL https://talos.dev/install | sh - if [ $? -ne 0 ]; then - echo "Error installing talosctl. Please check the installation script." - exit 1 - fi - echo "talosctl installed successfully." -fi - -# Check if gomplate is installed -if ! command -v gomplate &> /dev/null; then - echo "Installing gomplate" - curl -sSL https://github.com/hairyhenderson/gomplate/releases/latest/download/gomplate_linux-amd64 -o $HOME/.local/bin/gomplate - chmod +x $HOME/.local/bin/gomplate - echo "gomplate installed successfully." -fi - -# Install kustomize -if ! command -v kustomize &> /dev/null; then - echo "Installing kustomize" - curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash - mv kustomize $HOME/.local/bin/ - echo "kustomize installed successfully." -fi - -## Install yq -if ! command -v yq &> /dev/null; then - echo "Installing yq" - VERSION=v4.45.4 - BINARY=yq_linux_amd64 - wget https://github.com/mikefarah/yq/releases/download/${VERSION}/${BINARY}.tar.gz -O - | tar xz - mv ${BINARY} $HOME/.local/bin/yq - chmod +x $HOME/.local/bin/yq - rm yq.1 - echo "yq installed successfully." -fi - -KUBECONFIG=~/.kube/config -export KUBECONFIG - -# Use cluster name as both talos and kubectl context name -CLUSTER_NAME=$(wild-config cluster.name) -if [ -z "${CLUSTER_NAME}" ] || [ "${CLUSTER_NAME}" = "null" ]; then - echo "Error: cluster.name not set in config.yaml" -else - KUBE_CONTEXT="admin@${CLUSTER_NAME}" - CURRENT_KUBE_CONTEXT=$(kubectl config current-context) - if [ "${CURRENT_KUBE_CONTEXT}" != "${KUBE_CONTEXT}" ]; then - if kubectl config get-contexts | grep -q "${KUBE_CONTEXT}"; then - echo "Switching to kubernetes context ${KUBE_CONTEXT}" - else - echo "WARNING: Context ${KUBE_CONTEXT} does not exist." - # kubectl config set-context "${KUBE_CONTEXT}" --cluster="${CLUSTER_NAME}" --user=admin - fi - fi -fi diff --git a/test/test_common_functions.bats b/test/test_common_functions.bats deleted file mode 100644 index 052e0ac..0000000 --- a/test/test_common_functions.bats +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/env bats - -# test_common_functions.bats -# Tests for the wild-common.sh library functions - -load 'test_helper' - -setup() { - setup_test_project "common-test" - cd "$TEST_PROJECT_DIR" -} - -teardown() { - teardown_test_project "common-test" -} - -@test "find_wc_home from project root" { - cd "$TEST_PROJECT_DIR" - WC_HOME_RESULT=$(find_wc_home) - assert_equal "$WC_HOME_RESULT" "$TEST_PROJECT_DIR" -} - -@test "find_wc_home from nested subdirectory" { - mkdir -p "$TEST_PROJECT_DIR/deep/nested/path" - cd "$TEST_PROJECT_DIR/deep/nested/path" - WC_HOME_RESULT=$(find_wc_home) - assert_equal "$WC_HOME_RESULT" "$TEST_PROJECT_DIR" -} - -@test "find_wc_home when no project found" { - cd /tmp - run find_wc_home - assert_failure -} - -@test "init_wild_env sets WC_HOME correctly" { - mkdir -p "$TEST_PROJECT_DIR/deep/nested" - cd "$TEST_PROJECT_DIR/deep/nested" - unset WC_HOME - export WC_ROOT="$PROJECT_ROOT" - export PATH="$PROJECT_ROOT/bin:$PATH" - init_wild_env - assert_equal "$WC_HOME" "$TEST_PROJECT_DIR" -} - -@test "init_wild_env sets WC_ROOT correctly" { - cd "$TEST_PROJECT_DIR" - unset WC_HOME - export WC_ROOT="$PROJECT_ROOT" - export PATH="$PROJECT_ROOT/bin:$PATH" - init_wild_env - # WC_ROOT is set (value depends on test execution context) - assert [ -n "$WC_ROOT" ] -} - -@test "check_wild_directory passes when in project" { - cd "$TEST_PROJECT_DIR" - run check_wild_directory - assert_success -} - -@test "print functions work correctly" { - cd "$TEST_PROJECT_DIR" - run bash -c ' - source "$PROJECT_ROOT/scripts/common.sh" - print_header "Test Header" - print_info "Test info message" - print_warning "Test warning message" - print_success "Test success message" - print_error "Test error message" - ' - assert_success - assert_output --partial "Test Header" - assert_output --partial "Test info message" -} - -@test "command_exists works for existing command" { - run command_exists "bash" - assert_success -} - -@test "command_exists fails for nonexistent command" { - run command_exists "nonexistent-command-xyz" - assert_failure -} - -@test "generate_random_string produces correct length" { - RANDOM_STR=$(generate_random_string 16) - assert_equal "${#RANDOM_STR}" "16" -} \ No newline at end of file diff --git a/test/test_config_functions.bats b/test/test_config_functions.bats deleted file mode 100644 index 36bb068..0000000 --- a/test/test_config_functions.bats +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env bats - -# test_config_functions.bats -# Tests for config and secret access functions - -load 'test_helper' - -setup() { - setup_test_project "config-test" - cd "$TEST_PROJECT_DIR" - init_wild_env -} - -teardown() { - teardown_test_project "config-test" -} - -@test "wild-config with existing config" { - CLUSTER_NAME=$(wild-config "cluster.name") - assert_equal "$CLUSTER_NAME" "test-cluster" -} - -@test "wild-config with nested path" { - VIP=$(wild-config "cluster.nodes.control.vip") - assert_equal "$VIP" "192.168.100.200" -} - -@test "wild-config with non-existent key" { - NONEXISTENT=$(wild-config "nonexistent.key") - assert_equal "$NONEXISTENT" "" -} - -@test "active nodes configuration access - interface" { - CONTROL_NODE_INTERFACE=$(wild-config "cluster.nodes.active.\"192.168.100.201\".interface") - assert_equal "$CONTROL_NODE_INTERFACE" "eth0" -} - -@test "active nodes configuration access - maintenance IP" { - MAINTENANCE_IP=$(wild-config "cluster.nodes.active.\"192.168.100.201\".maintenanceIp") - assert_equal "$MAINTENANCE_IP" "192.168.100.131" -} - -@test "wild-secret function" { - # Create temporary secrets file for testing - cp "$TEST_DIR/fixtures/sample-secrets.yaml" "$TEST_PROJECT_DIR/secrets.yaml" - - SECRET_VAL=$(wild-secret "operator.cloudflareApiToken") - assert_equal "$SECRET_VAL" "test_api_token_123456789" -} - -@test "config access from subdirectory" { - mkdir -p "$TEST_PROJECT_DIR/config-subdir" - cd "$TEST_PROJECT_DIR/config-subdir" - unset WC_HOME - export WC_ROOT="$PROJECT_ROOT" - export PATH="$PROJECT_ROOT/bin:$PATH" - init_wild_env - - SUBDIR_CLUSTER=$(wild-config "cluster.name") - assert_equal "$SUBDIR_CLUSTER" "test-cluster" -} \ No newline at end of file diff --git a/test/test_helper.bash b/test/test_helper.bash deleted file mode 100644 index f7770f6..0000000 --- a/test/test_helper.bash +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env bash - -# test_helper.bash -# Common setup and utilities for bats tests - -# Load bats helpers -load 'test_helper/bats-support/load' -load 'test_helper/bats-assert/load' - -# Test environment variables -export TEST_DIR="$(cd "$(dirname "${BATS_TEST_FILENAME}")" && pwd)" -export PROJECT_ROOT="$(dirname "$TEST_DIR")" -export TMP_DIR="$TEST_DIR/tmp" - -# Set up test environment -setup_test_project() { - local project_name="${1:-test-project}" - - # Create tmp directory - mkdir -p "$TMP_DIR" - - # Create test project - export TEST_PROJECT_DIR="$TMP_DIR/$project_name" - mkdir -p "$TEST_PROJECT_DIR/.wildcloud" - - # Copy fixture config if it exists - if [ -f "$TEST_DIR/fixtures/sample-config.yaml" ]; then - cp "$TEST_DIR/fixtures/sample-config.yaml" "$TEST_PROJECT_DIR/config.yaml" - fi - - # Source wild-common.sh - source "$PROJECT_ROOT/scripts/common.sh" -} - -# Clean up test environment -teardown_test_project() { - local project_name="${1:-test-project}" - - if [ -n "$TMP_DIR" ] && [ -d "$TMP_DIR" ]; then - rm -rf "$TMP_DIR/$project_name" - fi -} - -# Create additional test project -create_test_project() { - local project_name="$1" - local project_dir="$TMP_DIR/$project_name" - - mkdir -p "$project_dir/.wildcloud" - - # Copy fixture config if requested - if [ $# -gt 1 ] && [ "$2" = "with-config" ]; then - cp "$TEST_DIR/fixtures/sample-config.yaml" "$project_dir/config.yaml" - fi - - echo "$project_dir" -} - -# Remove additional test project -remove_test_project() { - local project_name="$1" - rm -rf "$TMP_DIR/$project_name" -} \ No newline at end of file diff --git a/test/test_helper/bats-assert b/test/test_helper/bats-assert deleted file mode 160000 index 912a988..0000000 --- a/test/test_helper/bats-assert +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 912a98804efd34f24d5eae1bf97ee622ca770e99 diff --git a/test/test_helper/bats-support b/test/test_helper/bats-support deleted file mode 160000 index 0ad082d..0000000 --- a/test/test_helper/bats-support +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0ad082d4590108684c68975ca517a90459f05cd0 diff --git a/test/test_project_detection.bats b/test/test_project_detection.bats deleted file mode 100644 index 8cb796c..0000000 --- a/test/test_project_detection.bats +++ /dev/null @@ -1,107 +0,0 @@ -#!/usr/bin/env bats - -# test_project_detection.bats -# Tests for wild-cloud project detection from various directory structures - -load 'test_helper' - -setup() { - setup_test_project "detection-test" -} - -teardown() { - teardown_test_project "detection-test" -} - -@test "script execution from project root" { - cd "$TEST_PROJECT_DIR" - run "$PROJECT_ROOT/bin/wild-cluster-node-up" --help - assert_success -} - -@test "script execution from nested subdirectory" { - mkdir -p "$TEST_PROJECT_DIR/deep/very/nested/path" - cd "$TEST_PROJECT_DIR/deep/very/nested/path" - run "$PROJECT_ROOT/bin/wild-cluster-node-up" --help - assert_success -} - -@test "wild-cluster-node-up works from subdirectory" { - mkdir -p "$TEST_PROJECT_DIR/subdir" - cd "$TEST_PROJECT_DIR/subdir" - run "$PROJECT_ROOT/bin/wild-cluster-node-up" --help - assert_success -} - -@test "wild-setup works from subdirectory" { - mkdir -p "$TEST_PROJECT_DIR/subdir" - cd "$TEST_PROJECT_DIR/subdir" - run "$PROJECT_ROOT/bin/wild-setup" --help - assert_success -} - -@test "wild-setup-cluster works from subdirectory" { - mkdir -p "$TEST_PROJECT_DIR/subdir" - cd "$TEST_PROJECT_DIR/subdir" - run "$PROJECT_ROOT/bin/wild-setup-cluster" --help - assert_success -} - -@test "wild-cluster-config-generate works from subdirectory" { - mkdir -p "$TEST_PROJECT_DIR/subdir" - cd "$TEST_PROJECT_DIR/subdir" - run "$PROJECT_ROOT/bin/wild-cluster-config-generate" --help - assert_success -} - -@test "config access from subdirectories" { - mkdir -p "$TEST_PROJECT_DIR/config-test" - cd "$TEST_PROJECT_DIR/config-test" - - # Set up environment like the scripts do - unset WC_HOME - export WC_ROOT="$PROJECT_ROOT" - export PATH="$PROJECT_ROOT/bin:$PATH" - init_wild_env - - CLUSTER_NAME=$("$PROJECT_ROOT/bin/wild-config" cluster.name 2>/dev/null) - assert_equal "$CLUSTER_NAME" "test-cluster" -} - -@test "environment variables from project root" { - cd "$TEST_PROJECT_DIR" - unset WC_HOME - export WC_ROOT="$PROJECT_ROOT" - export PATH="$PROJECT_ROOT/bin:$PATH" - source "$PROJECT_ROOT/scripts/common.sh" - init_wild_env - - assert_equal "$WC_HOME" "$TEST_PROJECT_DIR" - assert [ -n "$WC_ROOT" ] -} - -@test "environment variables from nested directory" { - mkdir -p "$TEST_PROJECT_DIR/deep/very" - cd "$TEST_PROJECT_DIR/deep/very" - unset WC_HOME - export WC_ROOT="$PROJECT_ROOT" - export PATH="$PROJECT_ROOT/bin:$PATH" - source "$PROJECT_ROOT/scripts/common.sh" - init_wild_env - - assert_equal "$WC_HOME" "$TEST_PROJECT_DIR" - assert [ -n "$WC_ROOT" ] -} - -@test "scripts fail gracefully outside project" { - # Create a temporary directory without .wildcloud - TEMP_NO_PROJECT=$(create_test_project "no-wildcloud") - rm -rf "$TEMP_NO_PROJECT/.wildcloud" - cd "$TEMP_NO_PROJECT" - - # The script should fail because check_wild_directory won't find .wildcloud - run "$PROJECT_ROOT/bin/wild-cluster-node-up" 192.168.1.1 --dry-run - assert_failure - - remove_test_project "no-wildcloud" -} \ No newline at end of file