Repository navigation
Expand file tree
/
Copy pathutils.sh
More file actions
160 lines (140 loc) · 4.62 KB
/
Copy pathutils.sh
File metadata and controls
160 lines (140 loc) · 4.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#!/bin/bash
# Shared utilities for test scripts
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Print functions
print_header() {
echo -e "${YELLOW}==== $1 ====${NC}"
}
print_success() {
echo -e "${GREEN}✓ $1${NC}"
}
print_error() {
echo -e "${RED}✗ $1${NC}"
}
# Get project root directory
get_project_root() {
cd "$(dirname "$0")/.." && pwd
}
# Check if command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# Check dependency and fail the calling test if not found.
#
# Returns rather than exits: a missing toolchain should fail its own test and
# let the rest of the suite run, not abort the whole script before the summary
# is printed.
check_dependency() {
local cmd=$1
if ! command_exists "${cmd}"; then
print_error "${cmd} is not installed"
return 1
fi
}
# The executable suffix for this platform: ".exe" under MSYS/Git Bash, empty
# elsewhere. Windows will not run a binary without it, whatever the file is
# actually named.
exe_suffix() {
case "$(uname -s)" in
MINGW* | MSYS* | CYGWIN*) echo ".exe" ;;
*) echo "" ;;
esac
}
# Echo the path to a built binary, accounting for the .exe suffix on Windows.
# Prints nothing and returns 1 when neither exists.
resolve_binary() {
local path=$1
if [ -f "${path}$(exe_suffix)" ]; then
echo "${path}$(exe_suffix)"
elif [ -f "${path}" ]; then
echo "${path}"
else
return 1
fi
}
# Setup test environment
setup_test() {
local test_name=$1
print_header "Testing ${test_name}"
PROJECT_ROOT=$(get_project_root)
SERVER_DIR="${PROJECT_ROOT}/${test_name}"
CLIENT_DIR="${PROJECT_ROOT}/${test_name}"
TEST_CLIENT="${PROJECT_ROOT}/tests/helpers/build/mcp-test-client.js"
MOCK_SERVER="${PROJECT_ROOT}/tests/helpers/build/mock-mcp-server.js"
TOOL_LOOP_TEST="${PROJECT_ROOT}/tests/helpers/build/tool-loop-test.js"
}
# Ensure test helpers are built
ensure_helpers_built() {
if [ ! -f "${TEST_CLIENT}" ] || [ ! -f "${MOCK_SERVER}" ] || [ ! -f "${TOOL_LOOP_TEST}" ]; then
print_error "Test helpers not built"
print_header "Building test helpers..."
cd "${PROJECT_ROOT}/tests/helpers" || return 1
npm install >/dev/null 2>&1
npm run build >/dev/null 2>&1
cd - >/dev/null || return 1
print_success "Test helpers built"
fi
}
# Run a build step, showing its output only if it fails.
#
# Builds used to be run with `>/dev/null 2>&1` unconditionally, which hid
# compile errors: a failed build left no binary behind and the smoke test
# reported the downstream `spawn ... ENOENT` instead of the actual error.
run_build() {
local what=$1
shift
local output
if ! output=$("$@" 2>&1); then
print_error "${what} failed"
echo "${output}"
return 1
fi
}
# Ensure a project directory is built (TypeScript/Rust/Go)
#
# `bin` is the name of the binary a Rust or Go project produces (unused for
# TypeScript). Rust names it after the package, so it is looked for under
# target/; Go is told to write it to `bin` in the project directory.
#
# Returns rather than exits, for the same reason check_dependency does: one
# project failing to build should fail its own test, not abort the suite
# before the summary is printed.
ensure_built() {
local dir=$1
local bin=$2
cd "${dir}" || return 1
# Install npm dependencies if needed
if [ -f "package.json" ] && [ ! -d "node_modules" ]; then
run_build "npm install in ${dir}" npm install || return 1
fi
# Build TypeScript if needed
if [ -f "tsconfig.json" ] && [ ! -f "build/index.js" ]; then
run_build "npm run build in ${dir}" npm run build || return 1
fi
# Build Rust if needed
if [ -f "Cargo.toml" ] \
&& ! resolve_binary "target/release/${bin}" >/dev/null \
&& ! resolve_binary "target/debug/${bin}" >/dev/null; then
run_build "cargo build in ${dir}" cargo build --release || return 1
fi
# Build Go if needed
if [ -f "go.mod" ] && ! resolve_binary "${bin}" >/dev/null; then
run_build "go build in ${dir}" go build -o "${bin}$(exe_suffix)" . || return 1
fi
}
# Ensure a Ruby project directory has its gems installed.
#
# Unlike ensure_built there is no artefact to test for -- a Ruby example has no
# build step, and a committed Gemfile.lock says which gems are wanted, not
# whether they are installed -- so ask Bundler directly.
ensure_bundled() {
local dir=$1
cd "${dir}" || return 1
if ! bundle check >/dev/null 2>&1; then
run_build "bundle install in ${dir}" bundle install || return 1
fi
}