diff options
| author | Filip Wandzio <contact@philw.dev> | 2026-03-01 01:03:39 +0100 |
|---|---|---|
| committer | Filip Wandzio <contact@philw.dev> | 2026-03-01 01:03:39 +0100 |
| commit | bf0d77d7d448e964e9716d5af67c48f3d014f090 (patch) | |
| tree | e55f1e91a8c20cd737dfb01dc12a954c25711e01 | |
| download | embedded_guardian-bf0d77d7d448e964e9716d5af67c48f3d014f090.tar.gz embedded_guardian-bf0d77d7d448e964e9716d5af67c48f3d014f090.zip | |
Scaffold basic project tree, implement benchmarking logic
Implement unit testing guardian
Diffstat (limited to '')
| -rw-r--r-- | .clang-format | 31 | ||||
| -rw-r--r-- | .clang-tidy | 144 | ||||
| -rw-r--r-- | .editorconfig | 22 | ||||
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | Dockerfile | 22 | ||||
| -rw-r--r-- | Makefile | 48 | ||||
| -rw-r--r-- | app/main.c | 25 | ||||
| -rw-r--r-- | benchmark/benchmark.c | 101 | ||||
| -rw-r--r-- | benchmark/benchmark.h | 76 | ||||
| -rw-r--r-- | benchmark/resource_usage.c | 17 | ||||
| -rw-r--r-- | benchmark/resource_usage.h | 6 | ||||
| -rw-r--r-- | include/syntax_essentials.h | 81 | ||||
| -rw-r--r-- | src/syntax_essentials.c | 176 | ||||
| -rw-r--r-- | tests/test_syntax_essentials.c | 125 |
14 files changed, 877 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..670c9d1 --- /dev/null +++ b/.clang-format | |||
| @@ -0,0 +1,31 @@ | |||
| 1 | Language: C | ||
| 2 | BasedOnStyle: LLVM | ||
| 3 | AccessModifierOffset: -4 | ||
| 4 | AlignAfterOpenBracket: Align | ||
| 5 | AlignConsecutiveAssignments: false | ||
| 6 | AlignConsecutiveDeclarations: false | ||
| 7 | AlignEscapedNewlines: Left | ||
| 8 | AlignOperands: true | ||
| 9 | AlignTrailingComments: true | ||
| 10 | AllowAllParametersOfDeclarationOnNextLine: false | ||
| 11 | AllowShortCaseLabelsOnASingleLine: false | ||
| 12 | AllowShortFunctionsOnASingleLine: InlineOnly | ||
| 13 | AllowShortIfStatementsOnASingleLine: false | ||
| 14 | AllowShortLoopsOnASingleLine: false | ||
| 15 | AlwaysBreakAfterReturnType: None | ||
| 16 | BinPackArguments: false | ||
| 17 | BinPackParameters: false | ||
| 18 | BreakBeforeBinaryOperators: NonAssignment | ||
| 19 | BreakBeforeBraces: Linux | ||
| 20 | BreakBeforeTernaryOperators: true | ||
| 21 | BreakConstructorInitializersBeforeComma: true | ||
| 22 | ColumnLimit: 80 | ||
| 23 | ConstructorInitializerAllOnOneLineOrOnePerLine: true | ||
| 24 | DerivePointerAlignment: false | ||
| 25 | IndentCaseLabels: true | ||
| 26 | IndentWidth: 8 | ||
| 27 | KeepEmptyLinesAtTheStartOfBlocks: false | ||
| 28 | PointerAlignment: Left | ||
| 29 | SpacesBeforeTrailingComments: 1 | ||
| 30 | TabWidth: 8 | ||
| 31 | UseTab: Always | ||
diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..ecb3675 --- /dev/null +++ b/.clang-tidy | |||
| @@ -0,0 +1,144 @@ | |||
| 1 | --- | ||
| 2 | Checks: '-*, | ||
| 3 | bugprone-argument-comment, | ||
| 4 | bugprone-assert-side-effect, | ||
| 5 | bugprone-bad-signal-to-kill-thread, | ||
| 6 | bugprone-branch-clone, | ||
| 7 | bugprone-copy-constructor-init, | ||
| 8 | bugprone-dangling-handle, | ||
| 9 | bugprone-dynamic-static-initializers, | ||
| 10 | bugprone-fold-init-type, | ||
| 11 | bugprone-forward-declaration-namespace, | ||
| 12 | bugprone-forwarding-reference-overload, | ||
| 13 | bugprone-inaccurate-erase, | ||
| 14 | bugprone-incorrect-roundings, | ||
| 15 | bugprone-integer-division, | ||
| 16 | bugprone-lambda-function-name, | ||
| 17 | bugprone-macro-parentheses, | ||
| 18 | bugprone-macro-repeated-side-effects, | ||
| 19 | bugprone-misplaced-operator-in-strlen-in-alloc, | ||
| 20 | bugprone-misplaced-pointer-arithmetic-in-alloc, | ||
| 21 | bugprone-misplaced-widening-cast, | ||
| 22 | bugprone-move-forwarding-reference, | ||
| 23 | bugprone-multiple-statement-macro, | ||
| 24 | bugprone-no-escape, | ||
| 25 | bugprone-parent-virtual-call, | ||
| 26 | bugprone-posix-return, | ||
| 27 | bugprone-reserved-identifier, | ||
| 28 | bugprone-sizeof-container, | ||
| 29 | bugprone-sizeof-expression, | ||
| 30 | bugprone-spuriously-wake-up-functions, | ||
| 31 | bugprone-string-constructor, | ||
| 32 | bugprone-string-integer-assignment, | ||
| 33 | bugprone-string-literal-with-embedded-nul, | ||
| 34 | bugprone-suspicious-enum-usage, | ||
| 35 | bugprone-suspicious-include, | ||
| 36 | bugprone-suspicious-memset-usage, | ||
| 37 | bugprone-suspicious-missing-comma, | ||
| 38 | bugprone-suspicious-semicolon, | ||
| 39 | bugprone-suspicious-string-compare, | ||
| 40 | bugprone-suspicious-memory-comparison, | ||
| 41 | bugprone-suspicious-realloc-usage, | ||
| 42 | bugprone-swapped-arguments, | ||
| 43 | bugprone-terminating-continue, | ||
| 44 | bugprone-throw-keyword-missing, | ||
| 45 | bugprone-too-small-loop-variable, | ||
| 46 | bugprone-undefined-memory-manipulation, | ||
| 47 | bugprone-undelegated-constructor, | ||
| 48 | bugprone-unhandled-self-assignment, | ||
| 49 | bugprone-unused-raii, | ||
| 50 | bugprone-unused-return-value, | ||
| 51 | bugprone-use-after-move, | ||
| 52 | bugprone-virtual-near-miss, | ||
| 53 | cert-dcl21-cpp, | ||
| 54 | cert-dcl58-cpp, | ||
| 55 | cert-err34-c, | ||
| 56 | cert-err52-cpp, | ||
| 57 | cert-err60-cpp, | ||
| 58 | cert-flp30-c, | ||
| 59 | cert-msc50-cpp, | ||
| 60 | cert-msc51-cpp, | ||
| 61 | cert-str34-c, | ||
| 62 | cppcoreguidelines-interfaces-global-init, | ||
| 63 | cppcoreguidelines-narrowing-conversions, | ||
| 64 | cppcoreguidelines-pro-type-member-init, | ||
| 65 | cppcoreguidelines-pro-type-static-cast-downcast, | ||
| 66 | cppcoreguidelines-slicing, | ||
| 67 | google-default-arguments, | ||
| 68 | google-runtime-operator, | ||
| 69 | hicpp-exception-baseclass, | ||
| 70 | hicpp-multiway-paths-covered, | ||
| 71 | misc-misplaced-const, | ||
| 72 | misc-new-delete-overloads, | ||
| 73 | misc-non-copyable-objects, | ||
| 74 | misc-throw-by-value-catch-by-reference, | ||
| 75 | misc-unconventional-assign-operator, | ||
| 76 | misc-uniqueptr-reset-release, | ||
| 77 | modernize-avoid-bind, | ||
| 78 | modernize-concat-nested-namespaces, | ||
| 79 | modernize-deprecated-headers, | ||
| 80 | modernize-deprecated-ios-base-aliases, | ||
| 81 | modernize-loop-convert, | ||
| 82 | modernize-make-shared, | ||
| 83 | modernize-make-unique, | ||
| 84 | modernize-pass-by-value, | ||
| 85 | modernize-raw-string-literal, | ||
| 86 | modernize-redundant-void-arg, | ||
| 87 | modernize-replace-auto-ptr, | ||
| 88 | modernize-replace-disallow-copy-and-assign-macro, | ||
| 89 | modernize-replace-random-shuffle, | ||
| 90 | modernize-return-braced-init-list, | ||
| 91 | modernize-shrink-to-fit, | ||
| 92 | modernize-unary-static-assert, | ||
| 93 | modernize-use-auto, | ||
| 94 | modernize-use-bool-literals, | ||
| 95 | modernize-use-emplace, | ||
| 96 | modernize-use-equals-default, | ||
| 97 | modernize-use-equals-delete, | ||
| 98 | modernize-use-nodiscard, | ||
| 99 | modernize-use-noexcept, | ||
| 100 | modernize-use-nullptr, | ||
| 101 | modernize-use-override, | ||
| 102 | modernize-use-transparent-functors, | ||
| 103 | modernize-use-uncaught-exceptions, | ||
| 104 | mpi-buffer-deref, | ||
| 105 | mpi-type-mismatch, | ||
| 106 | openmp-use-default-none, | ||
| 107 | performance-faster-string-find, | ||
| 108 | performance-for-range-copy, | ||
| 109 | performance-implicit-conversion-in-loop, | ||
| 110 | performance-inefficient-algorithm, | ||
| 111 | performance-inefficient-string-concatenation, | ||
| 112 | performance-inefficient-vector-operation, | ||
| 113 | performance-move-const-arg, | ||
| 114 | performance-move-constructor-init, | ||
| 115 | performance-no-automatic-move, | ||
| 116 | performance-noexcept-move-constructor, | ||
| 117 | performance-trivially-destructible, | ||
| 118 | performance-type-promotion-in-math-fn, | ||
| 119 | performance-unnecessary-copy-initialization, | ||
| 120 | performance-unnecessary-value-param, | ||
| 121 | portability-simd-intrinsics, | ||
| 122 | readability-avoid-const-params-in-decls, | ||
| 123 | readability-const-return-type, | ||
| 124 | readability-container-size-empty, | ||
| 125 | readability-convert-member-functions-to-static, | ||
| 126 | readability-delete-null-pointer, | ||
| 127 | readability-deleted-default, | ||
| 128 | readability-inconsistent-declaration-parameter-name, | ||
| 129 | readability-make-member-function-const, | ||
| 130 | readability-misleading-indentation, | ||
| 131 | readability-misplaced-array-index, | ||
| 132 | readability-non-const-parameter, | ||
| 133 | readability-redundant-control-flow, | ||
| 134 | readability-redundant-declaration, | ||
| 135 | readability-redundant-function-ptr-dereference, | ||
| 136 | readability-redundant-smartptr-get, | ||
| 137 | readability-redundant-string-cstr, | ||
| 138 | readability-redundant-string-init, | ||
| 139 | readability-simplify-subscript-expr, | ||
| 140 | readability-static-accessed-through-instance, | ||
| 141 | readability-static-definition-in-anonymous-namespace, | ||
| 142 | readability-string-compare, | ||
| 143 | readability-uniqueptr-delete-release, | ||
| 144 | readability-use-anyofallof' \ No newline at end of file | ||
diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..00c8da6 --- /dev/null +++ b/.editorconfig | |||
| @@ -0,0 +1,22 @@ | |||
| 1 | # EditorConfig dla Embedded C Project | ||
| 2 | root = true | ||
| 3 | |||
| 4 | [*] | ||
| 5 | charset = utf-8 | ||
| 6 | end_of_line = lf | ||
| 7 | insert_final_newline = true | ||
| 8 | trim_trailing_whitespace = true | ||
| 9 | max_line_length = 80 | ||
| 10 | |||
| 11 | [*.c] | ||
| 12 | indent_style = tab | ||
| 13 | indent_size = 8 | ||
| 14 | |||
| 15 | [*.h] | ||
| 16 | indent_style = tab | ||
| 17 | indent_size = 8 | ||
| 18 | |||
| 19 | [Makefile] | ||
| 20 | indent_style = tab | ||
| 21 | indent_size = 8 | ||
| 22 | max_line_length = 120 \ No newline at end of file | ||
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2299d85 --- /dev/null +++ b/.gitignore | |||
| @@ -0,0 +1,3 @@ | |||
| 1 | build/ | ||
| 2 | .env | ||
| 3 | .idea/ | ||
diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..30aceae --- /dev/null +++ b/Dockerfile | |||
| @@ -0,0 +1,22 @@ | |||
| 1 | # ================= BUILD STAGE ================= | ||
| 2 | FROM alpine:3.19 AS builder | ||
| 3 | WORKDIR /project | ||
| 4 | |||
| 5 | # Instalacja gcc/make (tylko build stage) | ||
| 6 | RUN apk add --no-cache gcc g++ make build-base bash | ||
| 7 | |||
| 8 | COPY . . | ||
| 9 | |||
| 10 | # Budowa statycznego hostowego binarza | ||
| 11 | RUN make clean && \ | ||
| 12 | CC="gcc" CFLAGS="-Wall -Wextra -Werror -std=c18 -Iinclude -Ibenchmark -static" make host | ||
| 13 | |||
| 14 | # ================= FINAL STAGE ================= | ||
| 15 | FROM scratch | ||
| 16 | WORKDIR /project | ||
| 17 | |||
| 18 | # Skopiuj statyczny binarz | ||
| 19 | COPY --from=builder /project/build/syntax_essentials_tests_host /syntax_essentials_tests_host | ||
| 20 | |||
| 21 | # ENTRYPOINT | ||
| 22 | ENTRYPOINT ["/syntax_essentials_tests_host"] | ||
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4d0bb31 --- /dev/null +++ b/Makefile | |||
| @@ -0,0 +1,48 @@ | |||
| 1 | # ================= COMPILER SETTINGS ================= | ||
| 2 | CC ?= gcc | ||
| 3 | CFLAGS ?= -Wall -Wextra -Werror -std=c18 -Iinclude -Ibenchmark | ||
| 4 | LDFLAGS ?= | ||
| 5 | |||
| 6 | SRC_DIR := src | ||
| 7 | TEST_DIR := tests | ||
| 8 | BENCH_DIR := benchmark | ||
| 9 | BUILD_DIR := build | ||
| 10 | |||
| 11 | SRC := $(SRC_DIR)/syntax_essentials.c benchmark/benchmark.c benchmark/resource_usage.c | ||
| 12 | TESTS := $(wildcard $(TEST_DIR)/*.c) | ||
| 13 | |||
| 14 | OUT_HOST := $(BUILD_DIR)/syntax_essentials_tests_host | ||
| 15 | |||
| 16 | # ================= TARGETS ================= | ||
| 17 | all: host | ||
| 18 | |||
| 19 | $(BUILD_DIR): | ||
| 20 | mkdir -p $(BUILD_DIR) | ||
| 21 | |||
| 22 | # ---------------- HOST BUILD (static) ---------------- | ||
| 23 | host: $(BUILD_DIR) $(SRC) $(TESTS) | ||
| 24 | $(CC) $(CFLAGS) -static $(SRC) $(TESTS) -o $(OUT_HOST) $(LDFLAGS) | ||
| 25 | @echo "[INFO] Host binary built: $(OUT_HOST)" | ||
| 26 | |||
| 27 | # ---------------- ESP32 SIM ---------------- | ||
| 28 | esp32_sim: host | ||
| 29 | @echo "[INFO] Running ESP32-S3 simulation in minimal Docker..." | ||
| 30 | docker build -t esp32-s3-sim . | ||
| 31 | docker run --rm --memory=6m --cpus=0.2 esp32-s3-sim | ||
| 32 | |||
| 33 | # ---------------- CLEAN ---------------- | ||
| 34 | clean: | ||
| 35 | rm -rf $(BUILD_DIR) | ||
| 36 | |||
| 37 | # ---------------- INFO ---------------- | ||
| 38 | info: | ||
| 39 | @echo "Host compiler: $(CC)" | ||
| 40 | @echo "Binary: $(OUT_HOST)" | ||
| 41 | |||
| 42 | # ---------------- HELP ---------------- | ||
| 43 | help: | ||
| 44 | @echo "Available targets:" | ||
| 45 | @echo " all / host - Build host binary (statically linked)" | ||
| 46 | @echo " esp32_sim - Build host binary + run ESP32-S3 Docker simulation" | ||
| 47 | @echo " clean - Remove build artifacts" | ||
| 48 | @echo " info - Show compiler and binary info" | ||
diff --git a/app/main.c b/app/main.c new file mode 100644 index 0000000..dd93e39 --- /dev/null +++ b/app/main.c | |||
| @@ -0,0 +1,25 @@ | |||
| 1 | #include "gatekeeper.h" | ||
| 2 | #include <stdio.h> | ||
| 3 | |||
| 4 | int main(void) { | ||
| 5 | char name[50]; | ||
| 6 | char index[20]; | ||
| 7 | |||
| 8 | printf("=== Embedded Gatekeeper ===\n"); | ||
| 9 | |||
| 10 | printf("Enter name: "); | ||
| 11 | scanf("%49s", name); | ||
| 12 | |||
| 13 | printf("Enter index (6 digits): "); | ||
| 14 | scanf("%19s", index); | ||
| 15 | |||
| 16 | int result = register_student(name, index); | ||
| 17 | |||
| 18 | if (result == 0) { | ||
| 19 | printf("\nAccess granted.\n"); | ||
| 20 | } else { | ||
| 21 | printf("\nAccess denied (error code %d).\n", result); | ||
| 22 | } | ||
| 23 | |||
| 24 | return 0; | ||
| 25 | } | ||
diff --git a/benchmark/benchmark.c b/benchmark/benchmark.c new file mode 100644 index 0000000..2fe3163 --- /dev/null +++ b/benchmark/benchmark.c | |||
| @@ -0,0 +1,101 @@ | |||
| 1 | #define _POSIX_C_SOURCE 199309L | ||
| 2 | #include "benchmark.h" | ||
| 3 | #include "resource_usage.h" | ||
| 4 | #include <stdio.h> | ||
| 5 | |||
| 6 | #if defined(_WIN32) || defined(_WIN64) | ||
| 7 | #include <windows.h> | ||
| 8 | #else | ||
| 9 | #include <time.h> | ||
| 10 | #endif | ||
| 11 | |||
| 12 | /* ANSI color codes */ | ||
| 13 | const char* COLOR_RED = "\033[31m"; | ||
| 14 | const char* COLOR_GREEN = "\033[32m"; | ||
| 15 | const char* COLOR_YELLOW = "\033[33m"; | ||
| 16 | const char* COLOR_RESET = "\033[0m"; | ||
| 17 | |||
| 18 | /* Maximum allowed average execution time per call in milliseconds */ | ||
| 19 | const double MAX_ALLOWED_MS = 0.05; | ||
| 20 | |||
| 21 | /* Default benchmark iteration counts */ | ||
| 22 | const size_t DEFAULT_BENCHMARKS = 100; | ||
| 23 | const size_t ITERATIONS_FAST = 1000; | ||
| 24 | const size_t ITERATIONS_SLOW = 10; | ||
| 25 | |||
| 26 | /* Volatile sink to prevent compiler optimizations removing the function call */ | ||
| 27 | static volatile int sink; | ||
| 28 | |||
| 29 | /** | ||
| 30 | * @brief Cross-platform monotonic time in milliseconds | ||
| 31 | */ | ||
| 32 | static double now_ms(void) | ||
| 33 | { | ||
| 34 | #if defined(_WIN32) || defined(_WIN64) | ||
| 35 | LARGE_INTEGER freq, counter; | ||
| 36 | QueryPerformanceFrequency(&freq); | ||
| 37 | QueryPerformanceCounter(&counter); | ||
| 38 | return (double)counter.QuadPart * 1000.0 / (double)freq.QuadPart; | ||
| 39 | #else | ||
| 40 | struct timespec ts; | ||
| 41 | clock_gettime(CLOCK_MONOTONIC, &ts); | ||
| 42 | return ts.tv_sec * 1000.0 + ts.tv_nsec / 1e6; | ||
| 43 | #endif | ||
| 44 | } | ||
| 45 | |||
| 46 | double benchmark_func(const function_to_benchmark func, | ||
| 47 | const char* arg, | ||
| 48 | size_t benchmarks) | ||
| 49 | { | ||
| 50 | if (!func) | ||
| 51 | return 0.0; | ||
| 52 | if (benchmarks == 0) | ||
| 53 | benchmarks = DEFAULT_BENCHMARKS; | ||
| 54 | size_t warmup = benchmarks / 10; | ||
| 55 | if (warmup > 1000) | ||
| 56 | warmup = 1000; | ||
| 57 | for (size_t i = 0; i < warmup; ++i) | ||
| 58 | sink = func(arg); | ||
| 59 | const double start = now_ms(); | ||
| 60 | for (size_t i = 0; i < benchmarks; ++i) | ||
| 61 | sink = func(arg); | ||
| 62 | const double end = now_ms(); | ||
| 63 | return (end - start) / benchmarks; | ||
| 64 | } | ||
| 65 | |||
| 66 | void report_result(const char* test_name, | ||
| 67 | const int passed, | ||
| 68 | const double avg_ms, | ||
| 69 | const double max_allowed_ms) | ||
| 70 | { | ||
| 71 | const int is_pass = passed && (avg_ms <= max_allowed_ms); | ||
| 72 | const char* status_color = is_pass ? COLOR_GREEN : COLOR_RED; | ||
| 73 | const char* status_text = is_pass ? "PASS" : "FAIL"; | ||
| 74 | |||
| 75 | if (!is_pass && avg_ms > max_allowed_ms) | ||
| 76 | status_text = "SLOW"; | ||
| 77 | |||
| 78 | printf("[avg %.6f ms] %s%s: %s%s\n", | ||
| 79 | avg_ms, | ||
| 80 | status_color, | ||
| 81 | status_text, | ||
| 82 | test_name, | ||
| 83 | COLOR_RESET); | ||
| 84 | |||
| 85 | if (!is_pass && avg_ms > max_allowed_ms) { | ||
| 86 | printf(" %sReason:%s exceeded %.3f ms limit\n", | ||
| 87 | COLOR_YELLOW, | ||
| 88 | COLOR_RESET, | ||
| 89 | max_allowed_ms); | ||
| 90 | } | ||
| 91 | } | ||
| 92 | |||
| 93 | void benchmark_func_with_resources(const function_to_benchmark func, | ||
| 94 | const char* arg, | ||
| 95 | const size_t benchmarks, | ||
| 96 | const char* label) | ||
| 97 | { | ||
| 98 | const double avg_ms = benchmark_func(func, arg, benchmarks); | ||
| 99 | printf("[Benchmark] %s avg %.6f ms\n", label, avg_ms); | ||
| 100 | print_resource_usage(label); | ||
| 101 | } | ||
diff --git a/benchmark/benchmark.h b/benchmark/benchmark.h new file mode 100644 index 0000000..a47e2a6 --- /dev/null +++ b/benchmark/benchmark.h | |||
| @@ -0,0 +1,76 @@ | |||
| 1 | #pragma once | ||
| 2 | /** | ||
| 3 | * @file benchmark.h | ||
| 4 | * @brief High-resolution benchmark helper for embedded IoT/student functions. | ||
| 5 | * | ||
| 6 | * Provides functions to measure average execution time, report results | ||
| 7 | * with colors, and mark slow tests. | ||
| 8 | * | ||
| 9 | * PASS CRITERIA: | ||
| 10 | * - The function returns expected result | ||
| 11 | * - Average execution time <= MAX_ALLOWED_MS | ||
| 12 | * | ||
| 13 | * WCET STRATEGY: | ||
| 14 | * - Average latency is measured; for worst-case, run representative | ||
| 15 | * worst-case inputs, measure maximum, and apply safety margin. | ||
| 16 | * | ||
| 17 | * CROSS-PLATFORM: | ||
| 18 | * - Linux / POSIX: uses clock_gettime(CLOCK_MONOTONIC) | ||
| 19 | * - Windows: uses QueryPerformanceCounter | ||
| 20 | */ | ||
| 21 | |||
| 22 | #include <stddef.h> | ||
| 23 | |||
| 24 | /** ANSI color codes for terminal output */ | ||
| 25 | extern const char *COLOR_RED; | ||
| 26 | extern const char *COLOR_GREEN; | ||
| 27 | extern const char *COLOR_YELLOW; | ||
| 28 | extern const char *COLOR_RESET; | ||
| 29 | |||
| 30 | /** Default maximum allowed average execution time in milliseconds */ | ||
| 31 | extern const double MAX_ALLOWED_MS; | ||
| 32 | |||
| 33 | /** Default benchmark iteration counts */ | ||
| 34 | extern const size_t DEFAULT_BENCHMARKS; | ||
| 35 | extern const size_t ITERATIONS_FAST; | ||
| 36 | extern const size_t ITERATIONS_SLOW; | ||
| 37 | |||
| 38 | /** Function pointer type for benchmarked functions */ | ||
| 39 | typedef int (*function_to_benchmark)(const char *arg); | ||
| 40 | |||
| 41 | /** | ||
| 42 | * @brief Measure average execution time of a function. | ||
| 43 | * | ||
| 44 | * Runs the function `benchmarks` times, with warm-up, and returns | ||
| 45 | * average latency in milliseconds. | ||
| 46 | * | ||
| 47 | * @param func Function to benchmark. | ||
| 48 | * @param arg Argument to pass to function. | ||
| 49 | * @param benchmarks Number of iterations; 0 = DEFAULT_BENCHMARKS. | ||
| 50 | * @return Average latency per call in milliseconds. | ||
| 51 | */ | ||
| 52 | double benchmark_func(function_to_benchmark func, const char *arg, | ||
| 53 | size_t benchmarks); | ||
| 54 | |||
| 55 | /** | ||
| 56 | * @brief Print benchmark result with colors and performance threshold. | ||
| 57 | * | ||
| 58 | * If passed = 0 OR avg_ms > max_allowed_ms, result is marked FAIL/SLOW. | ||
| 59 | * | ||
| 60 | * @param test_name Name of test. | ||
| 61 | * @param passed Function correctness result (1 = ok, 0 = fail) | ||
| 62 | * @param avg_ms Measured average latency | ||
| 63 | * @param max_allowed_ms Threshold for marking slow | ||
| 64 | */ | ||
| 65 | void report_result(const char *test_name, int passed, double avg_ms, | ||
| 66 | double max_allowed_ms); | ||
| 67 | |||
| 68 | /** | ||
| 69 | * @brief | ||
| 70 | * @param func | ||
| 71 | * @param arg | ||
| 72 | * @param benchmarks | ||
| 73 | * @param label | ||
| 74 | */ | ||
| 75 | void benchmark_func_with_resources(function_to_benchmark func, const char *arg, | ||
| 76 | size_t benchmarks, const char *label); | ||
diff --git a/benchmark/resource_usage.c b/benchmark/resource_usage.c new file mode 100644 index 0000000..04f7195 --- /dev/null +++ b/benchmark/resource_usage.c | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | #include "resource_usage.h" | ||
| 2 | #include <stdio.h> | ||
| 3 | #include <sys/resource.h> | ||
| 4 | #include <sys/time.h> | ||
| 5 | |||
| 6 | void print_resource_usage(const char *label) { | ||
| 7 | struct rusage usage; | ||
| 8 | if (getrusage(RUSAGE_SELF, &usage) == 0) { | ||
| 9 | long mem_kb = usage.ru_maxrss; | ||
| 10 | double user_sec = usage.ru_utime.tv_sec + usage.ru_utime.tv_usec / 1e6; | ||
| 11 | double sys_sec = usage.ru_stime.tv_sec + usage.ru_stime.tv_usec / 1e6; | ||
| 12 | printf("[%s] CPU user: %.3f s, system: %.3f s, peak memory: %ld KB\n", | ||
| 13 | label, user_sec, sys_sec, mem_kb); | ||
| 14 | } else { | ||
| 15 | printf("[%s] Resource usage not available\n", label); | ||
| 16 | } | ||
| 17 | } | ||
diff --git a/benchmark/resource_usage.h b/benchmark/resource_usage.h new file mode 100644 index 0000000..7e2015c --- /dev/null +++ b/benchmark/resource_usage.h | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | #pragma once | ||
| 2 | /** | ||
| 3 | * | ||
| 4 | * @param label | ||
| 5 | */ | ||
| 6 | void print_resource_usage(const char *label); | ||
diff --git a/include/syntax_essentials.h b/include/syntax_essentials.h new file mode 100644 index 0000000..181cb99 --- /dev/null +++ b/include/syntax_essentials.h | |||
| @@ -0,0 +1,81 @@ | |||
| 1 | #pragma once | ||
| 2 | |||
| 3 | /** | ||
| 4 | * | ||
| 5 | * TASK 1: validate_first_name | ||
| 6 | * --------------------------- | ||
| 7 | * Check if the first name is valid. | ||
| 8 | * - Cannot be NULL or empty | ||
| 9 | * - Only letters (a-z, A-Z) | ||
| 10 | * - Minimum length: 2 | ||
| 11 | */ | ||
| 12 | int validate_first_name(const char *first_name); | ||
| 13 | |||
| 14 | /** | ||
| 15 | * TASK 2: validate_last_name | ||
| 16 | * -------------------------- | ||
| 17 | * Check if the last name is valid. | ||
| 18 | * - Cannot be NULL or empty | ||
| 19 | * - Only letters (a-z, A-Z) | ||
| 20 | * - Minimum length: 2 | ||
| 21 | */ | ||
| 22 | int validate_last_name(const char *last_name); | ||
| 23 | |||
| 24 | /** | ||
| 25 | * TASK 3: validate_full_name | ||
| 26 | * -------------------------- | ||
| 27 | * Check if a full name "First Last" is valid. | ||
| 28 | * Use TASK 1 and TASK 2 internally. | ||
| 29 | */ | ||
| 30 | int validate_full_name(const char *full_name); | ||
| 31 | |||
| 32 | /** | ||
| 33 | * TASK 4: validate_index | ||
| 34 | * ---------------------- | ||
| 35 | * Check if a student index is valid. | ||
| 36 | * - Exactly 6 digits | ||
| 37 | */ | ||
| 38 | int validate_index(const char *index); | ||
| 39 | |||
| 40 | /** | ||
| 41 | * TASK 5: register_student | ||
| 42 | * ------------------------ | ||
| 43 | * Register a student given full name and index. | ||
| 44 | * Return codes: | ||
| 45 | * 0 -> valid | ||
| 46 | * 1 -> invalid name | ||
| 47 | * 2 -> invalid index | ||
| 48 | */ | ||
| 49 | int register_student(const char *full_name, const char *index); | ||
| 50 | |||
| 51 | /** | ||
| 52 | * TASK 6: name_length | ||
| 53 | * ------------------ | ||
| 54 | * Return length of the string (do not use strlen). | ||
| 55 | */ | ||
| 56 | int name_length(const char *name); | ||
| 57 | |||
| 58 | /** | ||
| 59 | * TASK 7: student struct | ||
| 60 | * --------------------- | ||
| 61 | * Simple student structure | ||
| 62 | */ | ||
| 63 | typedef struct { | ||
| 64 | char full_name[64]; | ||
| 65 | char index[16]; | ||
| 66 | } Student; | ||
| 67 | |||
| 68 | /** | ||
| 69 | * TASK 8: add_student | ||
| 70 | * ------------------- | ||
| 71 | * Add a student to an array of Student. Return 0 if added. | ||
| 72 | */ | ||
| 73 | int add_student(Student *array, int max_size, const char *name, | ||
| 74 | const char *index); | ||
| 75 | |||
| 76 | /** | ||
| 77 | * TASK 9: get_error_message | ||
| 78 | * ------------------------ | ||
| 79 | * Return error message string for code returned by register_student | ||
| 80 | */ | ||
| 81 | const char *get_error_message(int code); | ||
diff --git a/src/syntax_essentials.c b/src/syntax_essentials.c new file mode 100644 index 0000000..419996a --- /dev/null +++ b/src/syntax_essentials.c | |||
| @@ -0,0 +1,176 @@ | |||
| 1 | #include "syntax_essentials.h" | ||
| 2 | #include <stdio.h> | ||
| 3 | |||
| 4 | /** | ||
| 5 | * | ||
| 6 | * TASK 1: validate_first_name | ||
| 7 | * Check if the first name is valid. | ||
| 8 | * - Cannot be NULL or empty | ||
| 9 | * - Only letters (a-z, A-Z) | ||
| 10 | * - Minimum length: 2 | ||
| 11 | * | ||
| 12 | * @param first_name | ||
| 13 | * @return | ||
| 14 | */ | ||
| 15 | int validate_first_name(const char* first_name) | ||
| 16 | { | ||
| 17 | if (!first_name) | ||
| 18 | return 0; | ||
| 19 | |||
| 20 | int length = 0; | ||
| 21 | for (const char* p = first_name; *p; p++) { | ||
| 22 | if ((*p >= 'A' && *p <= 'Z') || (*p >= 'a' && *p <= 'z')) | ||
| 23 | length++; | ||
| 24 | else | ||
| 25 | return 0; | ||
| 26 | } | ||
| 27 | return length >= 2; | ||
| 28 | } | ||
| 29 | |||
| 30 | /** | ||
| 31 | * | ||
| 32 | * TASK 2: validate_last_name | ||
| 33 | * Check if the last name is valid. | ||
| 34 | * - Cannot be NULL or empty | ||
| 35 | * - Only letters (a-z, A-Z) | ||
| 36 | * - Minimum length: 2 | ||
| 37 | * | ||
| 38 | * @param last_name | ||
| 39 | * @return | ||
| 40 | */ | ||
| 41 | int validate_last_name(const char* last_name) | ||
| 42 | { | ||
| 43 | if (!last_name) | ||
| 44 | return 0; | ||
| 45 | |||
| 46 | int length = 0; | ||
| 47 | |||
| 48 | for (const char* p = last_name; *p; p++) { | ||
| 49 | if ((*p >= 'A' && *p <= 'Z') || (*p >= 'a' && *p <= 'z')) | ||
| 50 | length++; | ||
| 51 | else | ||
| 52 | return 0; | ||
| 53 | } | ||
| 54 | |||
| 55 | return length >= 2; | ||
| 56 | } | ||
| 57 | |||
| 58 | /** | ||
| 59 | * | ||
| 60 | * TASK 3: validate_full_name | ||
| 61 | * Check if a full name "First Last" is valid. | ||
| 62 | * Use TASK 1 and TASK 2 internally. | ||
| 63 | * @param full_name | ||
| 64 | * @return | ||
| 65 | */ | ||
| 66 | int validate_full_name(const char* full_name) | ||
| 67 | { | ||
| 68 | if (!full_name) | ||
| 69 | return 0; | ||
| 70 | |||
| 71 | char first_name[64], last_name[64]; | ||
| 72 | if (sscanf(full_name, "%63s %63s", first_name, last_name) != 2) | ||
| 73 | return 0; | ||
| 74 | |||
| 75 | return validate_first_name(first_name) && validate_last_name(last_name); | ||
| 76 | } | ||
| 77 | |||
| 78 | /** | ||
| 79 | * | ||
| 80 | * @param index | ||
| 81 | * @return | ||
| 82 | */ | ||
| 83 | int validate_index(const char* index) | ||
| 84 | { | ||
| 85 | if (!index) | ||
| 86 | return 0; | ||
| 87 | |||
| 88 | int length = 0; | ||
| 89 | for (const char* p = index; *p; p++) { | ||
| 90 | if (*p >= '0' && *p <= '9') | ||
| 91 | length++; | ||
| 92 | else | ||
| 93 | return 0; | ||
| 94 | } | ||
| 95 | |||
| 96 | return length == 6; | ||
| 97 | } | ||
| 98 | |||
| 99 | /** | ||
| 100 | * | ||
| 101 | * @param full_name | ||
| 102 | * @param index | ||
| 103 | * @return | ||
| 104 | */ | ||
| 105 | int register_student(const char* full_name, const char* index) | ||
| 106 | { | ||
| 107 | if (!validate_full_name(full_name)) | ||
| 108 | return 1; | ||
| 109 | if (!validate_index(index)) | ||
| 110 | return 2; | ||
| 111 | return 0; | ||
| 112 | } | ||
| 113 | |||
| 114 | /** | ||
| 115 | * | ||
| 116 | * @param name | ||
| 117 | * @return | ||
| 118 | */ | ||
| 119 | int name_length(const char* name) | ||
| 120 | { | ||
| 121 | if (!name) | ||
| 122 | return 0; | ||
| 123 | |||
| 124 | int length = 0; | ||
| 125 | while (*name++) | ||
| 126 | length++; | ||
| 127 | return length; | ||
| 128 | } | ||
| 129 | |||
| 130 | /** | ||
| 131 | * | ||
| 132 | * @param array | ||
| 133 | * @param max_size | ||
| 134 | * @param name | ||
| 135 | * @param index | ||
| 136 | * @return | ||
| 137 | */ | ||
| 138 | int add_student(Student* array, | ||
| 139 | const int max_size, | ||
| 140 | const char* name, | ||
| 141 | const char* index) | ||
| 142 | { | ||
| 143 | if (!name || !index || !array) | ||
| 144 | return 2; | ||
| 145 | |||
| 146 | int len = 0; | ||
| 147 | while (len < max_size && array[len].full_name[0] != '\0') | ||
| 148 | len++; | ||
| 149 | |||
| 150 | if (len >= max_size) | ||
| 151 | return 1; | ||
| 152 | |||
| 153 | snprintf( | ||
| 154 | array[len].full_name, sizeof(array[len].full_name), "%s", name); | ||
| 155 | snprintf(array[len].index, sizeof(array[len].index), "%s", index); | ||
| 156 | return 0; | ||
| 157 | } | ||
| 158 | |||
| 159 | /** | ||
| 160 | * | ||
| 161 | * @param code | ||
| 162 | * @return | ||
| 163 | */ | ||
| 164 | const char* get_error_message(const int code) | ||
| 165 | { | ||
| 166 | switch (code) { | ||
| 167 | case 0: | ||
| 168 | return "Success"; | ||
| 169 | case 1: | ||
| 170 | return "Invalid name"; | ||
| 171 | case 2: | ||
| 172 | return "Invalid index"; | ||
| 173 | default: | ||
| 174 | return "Unknown error"; | ||
| 175 | } | ||
| 176 | } | ||
diff --git a/tests/test_syntax_essentials.c b/tests/test_syntax_essentials.c new file mode 100644 index 0000000..e0f0744 --- /dev/null +++ b/tests/test_syntax_essentials.c | |||
| @@ -0,0 +1,125 @@ | |||
| 1 | #include "benchmark.h" | ||
| 2 | #include "syntax_essentials.h" | ||
| 3 | #include <stdio.h> | ||
| 4 | |||
| 5 | static int total_tests = 0; | ||
| 6 | static int passed_tests = 0; | ||
| 7 | static int any_failure = 0; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * | ||
| 11 | * @param name | ||
| 12 | * @param func | ||
| 13 | * @param arg | ||
| 14 | * @param expected | ||
| 15 | * @param iterations | ||
| 16 | */ | ||
| 17 | static void run_test(const char* name, | ||
| 18 | const function_to_benchmark func, | ||
| 19 | const char* arg, | ||
| 20 | const int expected, | ||
| 21 | const size_t iterations) | ||
| 22 | { | ||
| 23 | total_tests++; | ||
| 24 | |||
| 25 | const double avg_ms = benchmark_func(func, arg, iterations); | ||
| 26 | const int result = func(arg); | ||
| 27 | |||
| 28 | const int logic_ok = (result == expected); | ||
| 29 | const int perf_ok = (avg_ms <= MAX_ALLOWED_MS); | ||
| 30 | const int ok = logic_ok && perf_ok; | ||
| 31 | |||
| 32 | if (ok) | ||
| 33 | passed_tests++; | ||
| 34 | else | ||
| 35 | any_failure = 1; | ||
| 36 | |||
| 37 | report_result(name, ok, avg_ms, MAX_ALLOWED_MS); | ||
| 38 | } | ||
| 39 | |||
| 40 | /** | ||
| 41 | * | ||
| 42 | */ | ||
| 43 | static void test_names(void) | ||
| 44 | { | ||
| 45 | run_test("validate_first_name: valid", | ||
| 46 | validate_first_name, | ||
| 47 | "Jan", | ||
| 48 | 1, | ||
| 49 | ITERATIONS_FAST); | ||
| 50 | run_test("validate_first_name: too short", | ||
| 51 | validate_first_name, | ||
| 52 | "J", | ||
| 53 | 0, | ||
| 54 | ITERATIONS_FAST); | ||
| 55 | run_test("validate_last_name: valid", | ||
| 56 | validate_last_name, | ||
| 57 | "Kowalski", | ||
| 58 | 1, | ||
| 59 | ITERATIONS_FAST); | ||
| 60 | run_test("validate_last_name: invalid char", | ||
| 61 | validate_last_name, | ||
| 62 | "Kowalski3", | ||
| 63 | 0, | ||
| 64 | ITERATIONS_FAST); | ||
| 65 | } | ||
| 66 | |||
| 67 | /** | ||
| 68 | * | ||
| 69 | */ | ||
| 70 | static void test_full_name(void) | ||
| 71 | { | ||
| 72 | run_test("validate_full_name: valid", | ||
| 73 | validate_full_name, | ||
| 74 | "Jan Kowalski", | ||
| 75 | 1, | ||
| 76 | ITERATIONS_SLOW); | ||
| 77 | run_test("validate_full_name: invalid first", | ||
| 78 | validate_full_name, | ||
| 79 | "J Kowalski", | ||
| 80 | 0, | ||
| 81 | ITERATIONS_SLOW); | ||
| 82 | } | ||
| 83 | |||
| 84 | /** | ||
| 85 | * | ||
| 86 | */ | ||
| 87 | static void test_index(void) | ||
| 88 | { | ||
| 89 | run_test("validate_index: valid", | ||
| 90 | validate_index, | ||
| 91 | "123456", | ||
| 92 | 1, | ||
| 93 | ITERATIONS_FAST); | ||
| 94 | run_test("validate_index: too short", | ||
| 95 | validate_index, | ||
| 96 | "12345", | ||
| 97 | 0, | ||
| 98 | ITERATIONS_FAST); | ||
| 99 | } | ||
| 100 | |||
| 101 | /** | ||
| 102 | * | ||
| 103 | * @return | ||
| 104 | */ | ||
| 105 | int main(void) | ||
| 106 | { | ||
| 107 | printf("=== Embedded Syntax Essentials Test Suite ===\n"); | ||
| 108 | |||
| 109 | test_names(); | ||
| 110 | test_full_name(); | ||
| 111 | test_index(); | ||
| 112 | |||
| 113 | printf("\n====================================\n"); | ||
| 114 | printf("Tests passed: %d / %d\n", passed_tests, total_tests); | ||
| 115 | |||
| 116 | if (!any_failure) { | ||
| 117 | printf( | ||
| 118 | "%sALL CRITERIA MET — PASS%s\n", COLOR_GREEN, COLOR_RESET); | ||
| 119 | return 0; | ||
| 120 | } | ||
| 121 | printf("%sSUITE FAILED — performance or logic violation%s\n", | ||
| 122 | COLOR_RED, | ||
| 123 | COLOR_RESET); | ||
| 124 | return 1; | ||
| 125 | } | ||
