test: add test_heavy.sh — big code, CPU, mem, archive, rapid, parallel, slow
This commit is contained in:
Executable
+994
@@ -0,0 +1,994 @@
|
||||
#!/usr/bin/env bash
|
||||
# test_heavy.sh — поиск багов: большой код, CPU-нагрузка, большой ответ,
|
||||
# архивы из ZIP, быстрые серии invoke, параллельные вызовы,
|
||||
# медленные функции, давление по памяти.
|
||||
# Все 4 языка: Python, Node.js, Ruby, PHP.
|
||||
#
|
||||
# ВАЖНО: Ruby и PHP invoke ранее не тестировались вообще!
|
||||
# Умеренная нагрузка (кластер небольшой, но нагрузка реальная).
|
||||
#
|
||||
# Запуск: bash scripts/test_heavy.sh
|
||||
# На ВМ: bash ~/terra/fission/scripts/test_heavy.sh
|
||||
set -uo pipefail
|
||||
|
||||
ROOT_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
source "${ROOT_DIR}/lib.sh"
|
||||
|
||||
RUN_ID=$(mk_run_id)
|
||||
SUB="livetest@test.local"
|
||||
TMP=$(mktemp -d)
|
||||
|
||||
echo ""
|
||||
echo "════════════════════════════════════════════════════════"
|
||||
echo " test_heavy.sh run=${RUN_ID}"
|
||||
echo "════════════════════════════════════════════════════════"
|
||||
|
||||
declare -a ALL_FNS=()
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$TMP" 2>/dev/null || true
|
||||
for fn in "${ALL_FNS[@]:-}"; do
|
||||
curl -s -X DELETE "${BASE}/functions/${fn}" \
|
||||
-H "X-Auth-Token: ${SUB}" >/dev/null 2>&1 || true
|
||||
done
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# ─── helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
# reg FN — регистрирует имя функции для cleanup
|
||||
reg() { ALL_FNS+=("$1"); }
|
||||
|
||||
# create_json LANG FN CODE_FILE — создаёт функцию из файла с кодом
|
||||
create_json() {
|
||||
local lang="$1" fn="$2" codefile="$3"
|
||||
local code_escaped
|
||||
code_escaped=$(cat "$codefile" | json_escape)
|
||||
reg "$fn"
|
||||
local R
|
||||
R=$(curl -s -w "\n%{http_code}" -X POST "${BASE}/functions" \
|
||||
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" \
|
||||
-d "{\"name\":\"${fn}\",\"language\":\"${lang}\",\"code\":${code_escaped}}" 2>/dev/null)
|
||||
check_http "$(echo "$R" | tail -1)" "201" "${lang^^} ${fn} CREATE → 201"
|
||||
}
|
||||
|
||||
# invoke_once FN EXPECTED LABEL — один invoke, без retry
|
||||
invoke_once() {
|
||||
local fn="$1" expected="$2" label="$3"
|
||||
local resp status raw err
|
||||
resp=$(curl -s --max-time 60 -X POST "${BASE}/functions/${fn}/invoke" \
|
||||
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" -d '{}')
|
||||
status=$(json_field "$resp" status)
|
||||
raw=$(json_field "$resp" response_raw)
|
||||
err=$(json_field "$resp" error)
|
||||
echo " fn=$fn status=$status raw=$(printf '%s' "$raw" | head -c 100) err=$(printf '%s' "$err" | head -c 80)"
|
||||
if [ "$status" = "200" ] && printf '%s' "$raw" | grep -qF "$expected"; then
|
||||
pass "$label → 200 + '${expected}'"
|
||||
return 0
|
||||
else
|
||||
fail "$label → status=${status:-?} (ожидали 200 + '${expected}')"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# SECTION 1: БОЛЬШОЙ КОД — ~100 строк на язык, из inline-кода
|
||||
# Ищем: лимиты на размер кода, проблемы загрузки, синтаксические баги
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
echo ""
|
||||
echo "────────────────────────────────────────────────────────"
|
||||
echo " S1: БОЛЬШОЙ КОД (Python / Node.js / Ruby / PHP)"
|
||||
echo "────────────────────────────────────────────────────────"
|
||||
|
||||
FN_BIG_PY="t-big-py-${RUN_ID}"
|
||||
cat > "${TMP}/big_py.py" << 'PYEOF'
|
||||
import math, hashlib, json
|
||||
|
||||
CONSTS = {f"k{i}": i * 3.14159265 for i in range(20)}
|
||||
|
||||
def fib(n):
|
||||
a, b = 0, 1
|
||||
for _ in range(n - 1): a, b = b, a + b
|
||||
return b
|
||||
|
||||
def sieve(limit):
|
||||
is_p = bytearray([1]) * (limit + 1)
|
||||
is_p[0] = is_p[1] = 0
|
||||
for i in range(2, int(limit**0.5) + 1):
|
||||
if is_p[i]:
|
||||
for j in range(i*i, limit+1, i): is_p[j] = 0
|
||||
return sum(is_p)
|
||||
|
||||
def rot13(s):
|
||||
return s.translate(str.maketrans(
|
||||
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
||||
'nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM'))
|
||||
|
||||
def caesar(s, shift=7):
|
||||
res = []
|
||||
for c in s:
|
||||
if 'a' <= c <= 'z': res.append(chr((ord(c) - 97 + shift) % 26 + 97))
|
||||
elif 'A' <= c <= 'Z': res.append(chr((ord(c) - 65 + shift) % 26 + 65))
|
||||
else: res.append(c)
|
||||
return ''.join(res)
|
||||
|
||||
def mat_trace(m):
|
||||
return sum(m[i][i] for i in range(len(m)))
|
||||
|
||||
def palindrome(s):
|
||||
t = s.lower().replace(' ', '')
|
||||
return t == t[::-1]
|
||||
|
||||
def flatten(lst):
|
||||
out = []
|
||||
for item in lst:
|
||||
if isinstance(item, list): out.extend(flatten(item))
|
||||
else: out.append(item)
|
||||
return out
|
||||
|
||||
def checksum(data):
|
||||
return hashlib.sha256(data.encode()).hexdigest()[:16]
|
||||
|
||||
def running_sum(lst):
|
||||
total = 0; out = []
|
||||
for v in lst: total += v; out.append(total)
|
||||
return out
|
||||
|
||||
def count_distinct(lst):
|
||||
return len(set(lst))
|
||||
|
||||
def group_by_mod(lst, n):
|
||||
g = {i: [] for i in range(n)}
|
||||
for v in lst: g[v % n].append(v)
|
||||
return {str(k): len(v) for k, v in g.items()}
|
||||
|
||||
def pascal_row(n):
|
||||
row = [1]
|
||||
for i in range(1, n + 1): row.append(row[-1] * (n - i + 1) // i)
|
||||
return row
|
||||
|
||||
def deduplicate(lst):
|
||||
seen = set(); out = []
|
||||
for v in lst:
|
||||
if v not in seen: seen.add(v); out.append(v)
|
||||
return out
|
||||
|
||||
def reverse_words(s):
|
||||
return ' '.join(s.split()[::-1])
|
||||
|
||||
def count_vowels(s):
|
||||
return sum(1 for c in s.lower() if c in 'aeiou')
|
||||
|
||||
def is_prime(n):
|
||||
if n < 2: return False
|
||||
if n == 2: return True
|
||||
if n % 2 == 0: return False
|
||||
for i in range(3, int(math.sqrt(n)) + 1, 2):
|
||||
if n % i == 0: return False
|
||||
return True
|
||||
|
||||
def main(event, context):
|
||||
mat = [[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]]
|
||||
data = list(range(1, 51))
|
||||
nested = [[1,[2,3]],[4,[5,[6,7]]],8]
|
||||
result = {
|
||||
"tag": "big-code-py-ok",
|
||||
"fib_25": fib(25),
|
||||
"primes_50": sieve(50),
|
||||
"rot13": rot13("Hello Fission"),
|
||||
"caesar": caesar("heavy"),
|
||||
"const_sum": round(sum(CONSTS.values()), 2),
|
||||
"mat_trace": mat_trace(mat),
|
||||
"palindrome": palindrome("racecar"),
|
||||
"distinct": count_distinct([1,2,2,3,3,3,4]),
|
||||
"flat_len": len(flatten(nested)),
|
||||
"checksum": checksum("fission-heavy-test"),
|
||||
"rsum_last": running_sum(data)[-1],
|
||||
"pascal_4": pascal_row(4),
|
||||
"dedup_len": len(deduplicate([1,2,2,3,3,3,4,5,5])),
|
||||
"rev_words": reverse_words("big code python test"),
|
||||
"vowels": count_vowels("fission heavy test"),
|
||||
"is_prime_97": is_prime(97),
|
||||
"groups_3": group_by_mod(data[:30], 3),
|
||||
}
|
||||
return json.dumps(result), 200
|
||||
PYEOF
|
||||
create_json "python" "$FN_BIG_PY" "${TMP}/big_py.py"
|
||||
|
||||
FN_BIG_NODE="t-big-node-${RUN_ID}"
|
||||
cat > "${TMP}/big_node.js" << 'JSEOF'
|
||||
const crypto = require('crypto');
|
||||
const CONSTS = {};
|
||||
for (let i = 0; i < 20; i++) CONSTS[`k${i}`] = i * Math.PI;
|
||||
|
||||
function fib(n) {
|
||||
let a = 0, b = 1;
|
||||
for (let i = 1; i < n; i++) { let t = a + b; a = b; b = t; }
|
||||
return b;
|
||||
}
|
||||
function sieve(limit) {
|
||||
const isP = new Uint8Array(limit + 1).fill(1);
|
||||
isP[0] = isP[1] = 0;
|
||||
for (let i = 2; i * i <= limit; i++)
|
||||
if (isP[i]) for (let j = i*i; j <= limit; j += i) isP[j] = 0;
|
||||
return isP.reduce((s, v) => s + v, 0);
|
||||
}
|
||||
function rot13(s) {
|
||||
return s.replace(/[a-zA-Z]/g, c => {
|
||||
const b = c <= 'Z' ? 65 : 97;
|
||||
return String.fromCharCode((c.charCodeAt(0) - b + 13) % 26 + b);
|
||||
});
|
||||
}
|
||||
function caesar(s, shift = 7) {
|
||||
return s.replace(/[a-zA-Z]/g, c => {
|
||||
const b = c <= 'Z' ? 65 : 97;
|
||||
return String.fromCharCode((c.charCodeAt(0) - b + shift) % 26 + b);
|
||||
});
|
||||
}
|
||||
function matTrace(m) { return m.reduce((s, row, i) => s + row[i], 0); }
|
||||
function palindrome(s) { const t = s.toLowerCase().replace(/ /g, ''); return t === t.split('').reverse().join(''); }
|
||||
function flatten(arr) { return arr.reduce((a, v) => a.concat(Array.isArray(v) ? flatten(v) : v), []); }
|
||||
function checksum(d) { return crypto.createHash('sha256').update(d).digest('hex').slice(0, 16); }
|
||||
function runningSum(arr) { let t = 0; return arr.map(v => (t += v, t)); }
|
||||
function countDistinct(arr) { return new Set(arr).size; }
|
||||
function deduplicate(arr) { const s = new Set(); return arr.filter(v => !s.has(v) && s.add(v)); }
|
||||
function groupByMod(arr, n) { const g = {}; for (let i = 0; i < n; i++) g[i] = 0; arr.forEach(v => g[v % n]++); return g; }
|
||||
function pascalRow(n) { const r = [1]; for (let i = 1; i <= n; i++) r.push((r[i-1] * (n-i+1) / i) | 0); return r; }
|
||||
function reverseWords(s) { return s.split(' ').reverse().join(' '); }
|
||||
function countVowels(s) { return (s.toLowerCase().match(/[aeiou]/g) || []).length; }
|
||||
function isPrime(n) {
|
||||
if (n < 2) return false;
|
||||
for (let i = 2; i * i <= n; i++) if (n % i === 0) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
const mat = [[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]];
|
||||
const nested = [[1,[2,3]],[4,[5,[6,7]]],8];
|
||||
|
||||
module.exports = async function(ctx) {
|
||||
const data = Array.from({length: 50}, (_, i) => i + 1);
|
||||
const result = {
|
||||
tag: "big-code-node-ok",
|
||||
fib_25: fib(25),
|
||||
primes_50: sieve(50),
|
||||
rot13: rot13("Hello Fission"),
|
||||
caesar: caesar("heavy"),
|
||||
const_sum: Math.round(Object.values(CONSTS).reduce((a,b) => a+b, 0) * 100) / 100,
|
||||
mat_trace: matTrace(mat),
|
||||
palindrome: palindrome("racecar"),
|
||||
distinct: countDistinct([1,2,2,3,3,3,4]),
|
||||
flat_len: flatten(nested).length,
|
||||
checksum: checksum("fission-heavy-test"),
|
||||
rsum_last: runningSum(data).at(-1),
|
||||
pascal_4: pascalRow(4),
|
||||
dedup_len: deduplicate([1,2,2,3,3,3,4,5,5]).length,
|
||||
rev_words: reverseWords("big code node test"),
|
||||
vowels: countVowels("fission heavy test"),
|
||||
is_prime_97: isPrime(97),
|
||||
groups_3: groupByMod(data.slice(0,30), 3),
|
||||
};
|
||||
return { status: 200, body: JSON.stringify(result) };
|
||||
};
|
||||
JSEOF
|
||||
create_json "nodejs" "$FN_BIG_NODE" "${TMP}/big_node.js"
|
||||
|
||||
FN_BIG_RUBY="t-big-ruby-${RUN_ID}"
|
||||
cat > "${TMP}/big_ruby.rb" << 'RBEOF'
|
||||
require 'json'
|
||||
require 'digest'
|
||||
|
||||
CONSTS = (0...20).map { |i| [:"k#{i}", i * Math::PI] }.to_h
|
||||
|
||||
def fib(n)
|
||||
a, b = 0, 1
|
||||
(n - 1).times { a, b = b, a + b }
|
||||
b
|
||||
end
|
||||
|
||||
def sieve(limit)
|
||||
is_p = Array.new(limit + 1, true)
|
||||
is_p[0] = is_p[1] = false
|
||||
(2..Math.sqrt(limit).to_i).each do |i|
|
||||
next unless is_p[i]
|
||||
(i*i..limit).step(i) { |j| is_p[j] = false }
|
||||
end
|
||||
is_p.count(true)
|
||||
end
|
||||
|
||||
def rot13(s)
|
||||
s.tr('a-zA-Z', 'n-za-mN-ZA-M')
|
||||
end
|
||||
|
||||
def caesar(s, shift = 7)
|
||||
s.chars.map do |c|
|
||||
if c =~ /[a-z]/ then ((c.ord - 97 + shift) % 26 + 97).chr
|
||||
elsif c =~ /[A-Z]/ then ((c.ord - 65 + shift) % 26 + 65).chr
|
||||
else c
|
||||
end
|
||||
end.join
|
||||
end
|
||||
|
||||
def mat_trace(m)
|
||||
m.each_with_index.sum { |row, i| row[i] }
|
||||
end
|
||||
|
||||
def palindrome?(s)
|
||||
t = s.downcase.delete(' ')
|
||||
t == t.reverse
|
||||
end
|
||||
|
||||
def flatten_nested(lst)
|
||||
lst.each_with_object([]) do |item, out|
|
||||
item.is_a?(Array) ? out.concat(flatten_nested(item)) : out << item
|
||||
end
|
||||
end
|
||||
|
||||
def checksum(data)
|
||||
Digest::SHA256.hexdigest(data)[0, 16]
|
||||
end
|
||||
|
||||
def running_sum(arr)
|
||||
total = 0
|
||||
arr.map { |v| total += v; total }
|
||||
end
|
||||
|
||||
def count_distinct(arr)
|
||||
arr.uniq.length
|
||||
end
|
||||
|
||||
def group_by_mod(arr, n)
|
||||
(0...n).each_with_object({}) { |i, h| h[i.to_s] = arr.count { |v| v % n == i } }
|
||||
end
|
||||
|
||||
def pascal_row(n)
|
||||
row = [1]
|
||||
(1..n).each { |i| row << row[-1] * (n - i + 1) / i }
|
||||
row
|
||||
end
|
||||
|
||||
def reverse_words(s)
|
||||
s.split.reverse.join(' ')
|
||||
end
|
||||
|
||||
def count_vowels(s)
|
||||
s.downcase.count('aeiou')
|
||||
end
|
||||
|
||||
def is_prime(n)
|
||||
return false if n < 2
|
||||
(2..Math.sqrt(n).to_i).none? { |i| n % i == 0 }
|
||||
end
|
||||
|
||||
def handler(ctx)
|
||||
mat = [[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]]
|
||||
data = (1..50).to_a
|
||||
nested = [[1,[2,3]],[4,[5,[6,7]]],8]
|
||||
result = {
|
||||
tag: "big-code-ruby-ok",
|
||||
fib_25: fib(25),
|
||||
primes_50: sieve(50),
|
||||
rot13: rot13("Hello Fission"),
|
||||
caesar: caesar("heavy"),
|
||||
const_sum: CONSTS.values.sum.round(2),
|
||||
mat_trace: mat_trace(mat),
|
||||
palindrome: palindrome?("racecar"),
|
||||
distinct: count_distinct([1,2,2,3,3,3,4]),
|
||||
flat_len: flatten_nested(nested).length,
|
||||
checksum: checksum("fission-heavy-test"),
|
||||
rsum_last: running_sum(data).last,
|
||||
pascal_4: pascal_row(4),
|
||||
rev_words: reverse_words("big code ruby test"),
|
||||
vowels: count_vowels("fission heavy test"),
|
||||
is_prime_97: is_prime(97),
|
||||
groups_3: group_by_mod(data.first(30), 3),
|
||||
}
|
||||
{ statusCode: 200, body: result.to_json }
|
||||
end
|
||||
RBEOF
|
||||
create_json "ruby" "$FN_BIG_RUBY" "${TMP}/big_ruby.rb"
|
||||
|
||||
FN_BIG_PHP="t-big-php-${RUN_ID}"
|
||||
cat > "${TMP}/big_php.php" << 'PHPEOF'
|
||||
<?php
|
||||
$CONSTS = [];
|
||||
for ($i = 0; $i < 20; $i++) $CONSTS["k{$i}"] = $i * M_PI;
|
||||
|
||||
function fib($n) {
|
||||
$a = 0; $b = 1;
|
||||
for ($i = 1; $i < $n; $i++) { $t = $a + $b; $a = $b; $b = $t; }
|
||||
return $b;
|
||||
}
|
||||
function sieve($limit) {
|
||||
$isP = array_fill(0, $limit + 1, true);
|
||||
$isP[0] = $isP[1] = false;
|
||||
for ($i = 2; $i * $i <= $limit; $i++)
|
||||
if ($isP[$i]) for ($j = $i*$i; $j <= $limit; $j += $i) $isP[$j] = false;
|
||||
return array_sum(array_map('intval', $isP));
|
||||
}
|
||||
function rot13($s) { return str_rot13($s); }
|
||||
function caesar($s, $shift = 7) {
|
||||
$r = '';
|
||||
for ($i = 0; $i < strlen($s); $i++) {
|
||||
$c = $s[$i];
|
||||
if ($c >= 'a' && $c <= 'z') $r .= chr((ord($c) - 97 + $shift) % 26 + 97);
|
||||
elseif ($c >= 'A' && $c <= 'Z') $r .= chr((ord($c) - 65 + $shift) % 26 + 65);
|
||||
else $r .= $c;
|
||||
}
|
||||
return $r;
|
||||
}
|
||||
function matTrace($m) {
|
||||
$s = 0;
|
||||
foreach ($m as $i => $row) $s += $row[$i];
|
||||
return $s;
|
||||
}
|
||||
function palindrome($s) {
|
||||
$t = str_replace(' ', '', strtolower($s));
|
||||
return $t === strrev($t);
|
||||
}
|
||||
function checksumStr($data) { return substr(hash('sha256', $data), 0, 16); }
|
||||
function runningSum($arr) {
|
||||
$t = 0; $out = [];
|
||||
foreach ($arr as $v) { $t += $v; $out[] = $t; }
|
||||
return $out;
|
||||
}
|
||||
function countDistinct($arr) { return count(array_unique($arr)); }
|
||||
function groupByMod($arr, $n) {
|
||||
$g = array_fill(0, $n, 0);
|
||||
foreach ($arr as $v) $g[$v % $n]++;
|
||||
return $g;
|
||||
}
|
||||
function pascalRow($n) {
|
||||
$row = [1];
|
||||
for ($i = 1; $i <= $n; $i++) $row[] = intdiv($row[$i-1] * ($n-$i+1), $i);
|
||||
return $row;
|
||||
}
|
||||
function reverseWords($s) { return implode(' ', array_reverse(explode(' ', $s))); }
|
||||
function countVowels($s) { return preg_match_all('/[aeiou]/i', $s); }
|
||||
function isPrime($n) {
|
||||
if ($n < 2) return false;
|
||||
for ($i = 2; $i * $i <= $n; $i++) if ($n % $i === 0) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function handler(array $ctx): array {
|
||||
global $CONSTS;
|
||||
$mat = [[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]];
|
||||
$data = range(1, 50);
|
||||
$constSum = 0; foreach ($CONSTS as $v) $constSum += $v;
|
||||
$rsum = runningSum($data);
|
||||
$result = [
|
||||
'tag' => 'big-code-php-ok',
|
||||
'fib_25' => fib(25),
|
||||
'primes_50' => sieve(50),
|
||||
'rot13' => rot13('Hello Fission'),
|
||||
'caesar' => caesar('heavy'),
|
||||
'const_sum' => round($constSum, 2),
|
||||
'mat_trace' => matTrace($mat),
|
||||
'palindrome' => palindrome('racecar'),
|
||||
'distinct' => countDistinct([1,2,2,3,3,3,4]),
|
||||
'checksum' => checksumStr('fission-heavy-test'),
|
||||
'rsum_last' => end($rsum),
|
||||
'pascal_4' => pascalRow(4),
|
||||
'rev_words' => reverseWords('big code php test'),
|
||||
'vowels' => countVowels('fission heavy test'),
|
||||
'is_prime_97'=> isPrime(97),
|
||||
'groups_3' => groupByMod(array_slice($data, 0, 30), 3),
|
||||
];
|
||||
return ['statusCode' => 200, 'body' => json_encode($result)];
|
||||
}
|
||||
PHPEOF
|
||||
create_json "php" "$FN_BIG_PHP" "${TMP}/big_php.php"
|
||||
|
||||
echo ""
|
||||
echo ">>> S1: invoke больших функций — ждём cold start всех 4 языков..."
|
||||
for fn_tag in \
|
||||
"${FN_BIG_PY}:big-code-py-ok:Python" \
|
||||
"${FN_BIG_NODE}:big-code-node-ok:Node.js" \
|
||||
"${FN_BIG_RUBY}:big-code-ruby-ok:Ruby" \
|
||||
"${FN_BIG_PHP}:big-code-php-ok:PHP"; do
|
||||
fn="${fn_tag%%:*}"; rest="${fn_tag#*:}"; tag="${rest%%:*}"; label="${rest#*:}"
|
||||
(
|
||||
if wait_invoke "$SUB" "$fn" "$tag" 20 6; then
|
||||
echo "OK"
|
||||
else
|
||||
echo "FAIL"
|
||||
fi
|
||||
) > "${TMP}/s1-${fn}.out" &
|
||||
done
|
||||
wait
|
||||
|
||||
for fn_tag in \
|
||||
"${FN_BIG_PY}:big-code-py-ok:Python" \
|
||||
"${FN_BIG_NODE}:big-code-node-ok:Node.js" \
|
||||
"${FN_BIG_RUBY}:big-code-ruby-ok:Ruby" \
|
||||
"${FN_BIG_PHP}:big-code-php-ok:PHP"; do
|
||||
fn="${fn_tag%%:*}"; rest="${fn_tag#*:}"; tag="${rest%%:*}"; label="${rest#*:}"
|
||||
result=$(cat "${TMP}/s1-${fn}.out" 2>/dev/null || echo "FAIL")
|
||||
if [ "$result" = "OK" ]; then
|
||||
pass "S1 $label BIG CODE invoke → 200"
|
||||
else
|
||||
fail "S1 $label BIG CODE invoke — не стабилизировался"
|
||||
fi
|
||||
done
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# SECTION 2: CPU BURN — рекурсивный fib, нагрузка на CPU пода
|
||||
# Ищем: OOM kill, timeout, зависание пула, медленный poolmgr
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
echo ""
|
||||
echo "────────────────────────────────────────────────────────"
|
||||
echo " S2: CPU BURN — рекурсивный fib (Python/Node.js/Ruby/PHP)"
|
||||
echo "────────────────────────────────────────────────────────"
|
||||
|
||||
FN_CPU_PY="t-cpu-py-${RUN_ID}"
|
||||
cat > "${TMP}/cpu_py.py" << 'PYEOF'
|
||||
def fib(n):
|
||||
if n < 2: return n
|
||||
return fib(n-1) + fib(n-2)
|
||||
|
||||
def main(event, context):
|
||||
result = fib(28)
|
||||
return "cpu-burn-py:" + str(result), 200
|
||||
PYEOF
|
||||
create_json "python" "$FN_CPU_PY" "${TMP}/cpu_py.py"
|
||||
|
||||
FN_CPU_NODE="t-cpu-node-${RUN_ID}"
|
||||
cat > "${TMP}/cpu_node.js" << 'JSEOF'
|
||||
function fib(n) {
|
||||
if (n < 2) return n;
|
||||
return fib(n-1) + fib(n-2);
|
||||
}
|
||||
module.exports = async function(ctx) {
|
||||
return { status: 200, body: "cpu-burn-node:" + fib(35) };
|
||||
};
|
||||
JSEOF
|
||||
create_json "nodejs" "$FN_CPU_NODE" "${TMP}/cpu_node.js"
|
||||
|
||||
FN_CPU_RUBY="t-cpu-ruby-${RUN_ID}"
|
||||
cat > "${TMP}/cpu_ruby.rb" << 'RBEOF'
|
||||
def fib(n)
|
||||
return n if n < 2
|
||||
fib(n-1) + fib(n-2)
|
||||
end
|
||||
|
||||
def handler(ctx)
|
||||
{ statusCode: 200, body: "cpu-burn-ruby:#{fib(25)}" }
|
||||
end
|
||||
RBEOF
|
||||
create_json "ruby" "$FN_CPU_RUBY" "${TMP}/cpu_ruby.rb"
|
||||
|
||||
FN_CPU_PHP="t-cpu-php-${RUN_ID}"
|
||||
cat > "${TMP}/cpu_php.php" << 'PHPEOF'
|
||||
<?php
|
||||
function fib($n) {
|
||||
if ($n < 2) return $n;
|
||||
return fib($n-1) + fib($n-2);
|
||||
}
|
||||
function handler(array $ctx): array {
|
||||
return ['statusCode' => 200, 'body' => 'cpu-burn-php:' . fib(28)];
|
||||
}
|
||||
PHPEOF
|
||||
create_json "php" "$FN_CPU_PHP" "${TMP}/cpu_php.php"
|
||||
|
||||
echo ""
|
||||
echo ">>> S2: invoke CPU-burn функций..."
|
||||
for fn_tag in \
|
||||
"${FN_CPU_PY}:cpu-burn-py:Python" \
|
||||
"${FN_CPU_NODE}:cpu-burn-node:Node.js" \
|
||||
"${FN_CPU_RUBY}:cpu-burn-ruby:Ruby" \
|
||||
"${FN_CPU_PHP}:cpu-burn-php:PHP"; do
|
||||
fn="${fn_tag%%:*}"; rest="${fn_tag#*:}"; tag="${rest%%:*}"; label="${rest#*:}"
|
||||
(
|
||||
if wait_invoke "$SUB" "$fn" "$tag" 18 6; then
|
||||
echo "OK"
|
||||
else
|
||||
echo "FAIL"
|
||||
fi
|
||||
) > "${TMP}/s2-${fn}.out" &
|
||||
done
|
||||
wait
|
||||
|
||||
for fn_tag in \
|
||||
"${FN_CPU_PY}:cpu-burn-py:Python" \
|
||||
"${FN_CPU_NODE}:cpu-burn-node:Node.js" \
|
||||
"${FN_CPU_RUBY}:cpu-burn-ruby:Ruby" \
|
||||
"${FN_CPU_PHP}:cpu-burn-php:PHP"; do
|
||||
fn="${fn_tag%%:*}"; rest="${fn_tag#*:}"; tag="${rest%%:*}"; label="${rest#*:}"
|
||||
result=$(cat "${TMP}/s2-${fn}.out" 2>/dev/null || echo "FAIL")
|
||||
if [ "$result" = "OK" ]; then
|
||||
pass "S2 $label CPU BURN invoke → 200"
|
||||
else
|
||||
fail "S2 $label CPU BURN — не стабилизировался"
|
||||
fi
|
||||
done
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# SECTION 3: БОЛЬШОЙ ОТВЕТ — 50KB body
|
||||
# Ищем: truncation, proxy memory issues, timeout на больших ответах
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
echo ""
|
||||
echo "────────────────────────────────────────────────────────"
|
||||
echo " S3: БОЛЬШОЙ ОТВЕТ — 50KB (Python + Node.js)"
|
||||
echo "────────────────────────────────────────────────────────"
|
||||
|
||||
FN_RESP_PY="t-resp-py-${RUN_ID}"
|
||||
cat > "${TMP}/resp_py.py" << 'PYEOF'
|
||||
def main(event, context):
|
||||
chunk = "FISSION-HEAVY-DATA-" * 100 # 1900 chars
|
||||
data = (chunk * 27)[:50000] # ровно 50000 chars
|
||||
return "bigresponse-py-ok:" + data[:20] + "..." + str(len(data)), 200
|
||||
PYEOF
|
||||
create_json "python" "$FN_RESP_PY" "${TMP}/resp_py.py"
|
||||
|
||||
FN_RESP_NODE="t-resp-node-${RUN_ID}"
|
||||
cat > "${TMP}/resp_node.js" << 'JSEOF'
|
||||
module.exports = async function(ctx) {
|
||||
const chunk = "FISSION-HEAVY-DATA-".repeat(100);
|
||||
const data = (chunk.repeat(27)).slice(0, 50000);
|
||||
return { status: 200, body: "bigresponse-node-ok:" + data.slice(0,20) + "..." + data.length };
|
||||
};
|
||||
JSEOF
|
||||
create_json "nodejs" "$FN_RESP_NODE" "${TMP}/resp_node.js"
|
||||
|
||||
echo ""
|
||||
echo ">>> S3: invoke и проверка размера ответа..."
|
||||
for fn_tag in \
|
||||
"${FN_RESP_PY}:bigresponse-py-ok:Python" \
|
||||
"${FN_RESP_NODE}:bigresponse-node-ok:Node.js"; do
|
||||
fn="${fn_tag%%:*}"; rest="${fn_tag#*:}"; tag="${rest%%:*}"; label="${rest#*:}"
|
||||
if wait_invoke "$SUB" "$fn" "$tag" 15 6; then
|
||||
# Проверяем реальный размер ответа
|
||||
RESP=$(curl -s --max-time 60 -X POST "${BASE}/functions/${fn}/invoke" \
|
||||
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" -d '{}')
|
||||
RAW=$(json_field "$RESP" response_raw)
|
||||
RAW_LEN=${#RAW}
|
||||
echo " response_raw length = $RAW_LEN chars"
|
||||
if [ "$RAW_LEN" -gt 40000 ]; then
|
||||
pass "S3 $label BIG RESPONSE size=$RAW_LEN (>40000)"
|
||||
else
|
||||
fail "S3 $label BIG RESPONSE слишком мал: size=$RAW_LEN"
|
||||
fi
|
||||
else
|
||||
fail "S3 $label BIG RESPONSE — invoke не стабилизировался"
|
||||
fi
|
||||
done
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# SECTION 4: АРХИВЫ ИЗ ZIP — создаём архивы на лету, все 4 языка
|
||||
# Ищем: баги пути upload vs inline, entrypoint defaults, storagesvc PV
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
echo ""
|
||||
echo "────────────────────────────────────────────────────────"
|
||||
echo " S4: АРХИВ ИЗ ZIP (Python / Node.js / Ruby / PHP)"
|
||||
echo "────────────────────────────────────────────────────────"
|
||||
|
||||
# --- Создаём ZIP-архивы на лету через python3 ---
|
||||
|
||||
# Python archive: main.py (entrypoint по умолчанию: main.main)
|
||||
python3 - > "${TMP}/arc-py.zip" << 'EOF'
|
||||
import zipfile, io, sys
|
||||
buf = io.BytesIO()
|
||||
code = """import json
|
||||
def main(event, context):
|
||||
return json.dumps({"tag": "archive-py-ok", "fib25": 75025}), 200
|
||||
"""
|
||||
with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as zf:
|
||||
zf.writestr('main.py', code)
|
||||
sys.stdout.buffer.write(buf.getvalue())
|
||||
EOF
|
||||
|
||||
# Node.js archive: main.js (entrypoint: main → файл main.js)
|
||||
python3 - > "${TMP}/arc-node.zip" << 'EOF'
|
||||
import zipfile, io, sys
|
||||
buf = io.BytesIO()
|
||||
code = """module.exports = async function(ctx) {
|
||||
return { status: 200, body: JSON.stringify({ tag: "archive-node-ok", fib25: 75025 }) };
|
||||
};
|
||||
"""
|
||||
pkg = '{"name":"heavy-node","version":"1.0.0"}'
|
||||
with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as zf:
|
||||
zf.writestr('main.js', code)
|
||||
zf.writestr('package.json', pkg)
|
||||
sys.stdout.buffer.write(buf.getvalue())
|
||||
EOF
|
||||
|
||||
# Ruby archive: handler.rb (entrypoint: handler)
|
||||
python3 - > "${TMP}/arc-ruby.zip" << 'EOF'
|
||||
import zipfile, io, sys
|
||||
buf = io.BytesIO()
|
||||
code = """require 'json'
|
||||
def handler(ctx)
|
||||
{ statusCode: 200, body: JSON.generate({ tag: "archive-ruby-ok", fib25: 75025 }) }
|
||||
end
|
||||
"""
|
||||
with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as zf:
|
||||
zf.writestr('handler.rb', code)
|
||||
sys.stdout.buffer.write(buf.getvalue())
|
||||
EOF
|
||||
|
||||
# PHP archive: main.php (entrypoint: main.php::handler)
|
||||
python3 - > "${TMP}/arc-php.zip" << 'EOF'
|
||||
import zipfile, io, sys
|
||||
buf = io.BytesIO()
|
||||
code = """<?php
|
||||
function handler(array $ctx): array {
|
||||
return ['statusCode' => 200, 'body' => json_encode(['tag' => 'archive-php-ok', 'fib25' => 75025])];
|
||||
}
|
||||
"""
|
||||
with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as zf:
|
||||
zf.writestr('main.php', code)
|
||||
sys.stdout.buffer.write(buf.getvalue())
|
||||
EOF
|
||||
|
||||
# Загружаем архивы через multipart POST /functions
|
||||
declare -A ARC_LANGS=( ["py"]="python" ["node"]="nodejs" ["ruby"]="ruby" ["php"]="php" )
|
||||
declare -A ARC_TAGS=( ["py"]="archive-py-ok" ["node"]="archive-node-ok" ["ruby"]="archive-ruby-ok" ["php"]="archive-php-ok" )
|
||||
|
||||
for lang_short in py node ruby php; do
|
||||
fn="t-arc-${lang_short}-${RUN_ID}"
|
||||
reg "$fn"
|
||||
lang="${ARC_LANGS[$lang_short]}"
|
||||
arcfile="${TMP}/arc-${lang_short}.zip"
|
||||
|
||||
R=$(curl -s -w "\n%{http_code}" -X POST "${BASE}/functions" \
|
||||
-H "X-Auth-Token: ${SUB}" \
|
||||
-F "name=${fn}" \
|
||||
-F "language=${lang}" \
|
||||
-F "archive=@${arcfile};type=application/zip" 2>/dev/null)
|
||||
CODE=$(echo "$R" | tail -1)
|
||||
BODY=$(echo "$R" | head -n -1)
|
||||
check_http "$CODE" "201" "S4 ${lang_short^^} ARCHIVE upload → 201"
|
||||
if [ "$CODE" != "201" ]; then
|
||||
echo " upload error: $(printf '%s' "$BODY" | head -c 200)"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo ">>> S4: invoke архивных функций — ждём cold start..."
|
||||
for lang_short in py node ruby php; do
|
||||
fn="t-arc-${lang_short}-${RUN_ID}"
|
||||
tag="${ARC_TAGS[$lang_short]}"
|
||||
(
|
||||
if wait_invoke "$SUB" "$fn" "$tag" 20 6; then
|
||||
echo "OK"
|
||||
else
|
||||
echo "FAIL"
|
||||
fi
|
||||
) > "${TMP}/s4-${fn}.out" &
|
||||
done
|
||||
wait
|
||||
|
||||
for lang_short in py node ruby php; do
|
||||
fn="t-arc-${lang_short}-${RUN_ID}"
|
||||
tag="${ARC_TAGS[$lang_short]}"
|
||||
result=$(cat "${TMP}/s4-${fn}.out" 2>/dev/null || echo "FAIL")
|
||||
if [ "$result" = "OK" ]; then
|
||||
pass "S4 ${lang_short^^} ARCHIVE invoke → 200"
|
||||
else
|
||||
fail "S4 ${lang_short^^} ARCHIVE invoke — не стабилизировался"
|
||||
fi
|
||||
done
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# SECTION 5: БЫСТРЫЕ СЕРИИ — 12 последовательных invoke одной функции
|
||||
# Ищем: poolmgr reuse bugs, утечки памяти, деградацию latency
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
echo ""
|
||||
echo "────────────────────────────────────────────────────────"
|
||||
echo " S5: БЫСТРЫЕ СЕРИИ — 12x invoke подряд (Python)"
|
||||
echo "────────────────────────────────────────────────────────"
|
||||
|
||||
FN_RAPID="t-rapid-py-${RUN_ID}"
|
||||
cat > "${TMP}/rapid_py.py" << 'PYEOF'
|
||||
import time
|
||||
_count = 0
|
||||
def main(event, context):
|
||||
global _count
|
||||
_count += 1
|
||||
return "rapid-ok:{}".format(_count), 200
|
||||
PYEOF
|
||||
create_json "python" "$FN_RAPID" "${TMP}/rapid_py.py"
|
||||
|
||||
echo ">>> Ждём cold start..."
|
||||
if wait_invoke "$SUB" "$FN_RAPID" "rapid-ok" 15 5; then
|
||||
echo ">>> S5: Запускаем 12 последовательных invoke..."
|
||||
RAPID_OK=0
|
||||
RAPID_FAIL=0
|
||||
LATENCIES=()
|
||||
for i in $(seq 1 12); do
|
||||
T_START=$(date +%s%3N)
|
||||
RESP=$(curl -s --max-time 20 -X POST "${BASE}/functions/${FN_RAPID}/invoke" \
|
||||
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" -d '{}')
|
||||
T_END=$(date +%s%3N)
|
||||
LAT=$(( T_END - T_START ))
|
||||
STATUS=$(json_field "$RESP" status)
|
||||
RAW=$(json_field "$RESP" response_raw)
|
||||
echo " invoke ${i}/12: status=$STATUS raw=$(printf '%s' "$RAW" | head -c 40) latency=${LAT}ms"
|
||||
if [ "$STATUS" = "200" ]; then
|
||||
RAPID_OK=$((RAPID_OK+1))
|
||||
LATENCIES+=("$LAT")
|
||||
else
|
||||
RAPID_FAIL=$((RAPID_FAIL+1))
|
||||
fi
|
||||
done
|
||||
[ "$RAPID_FAIL" -eq 0 ] && pass "S5 RAPID FIRE: 12/12 invoke → 200" \
|
||||
|| fail "S5 RAPID FIRE: ${RAPID_FAIL}/12 упали"
|
||||
# Статистика latency
|
||||
if [ "${#LATENCIES[@]}" -gt 0 ]; then
|
||||
LAT_MIN="${LATENCIES[0]}"; LAT_MAX="${LATENCIES[0]}"
|
||||
for lat in "${LATENCIES[@]}"; do
|
||||
[ "$lat" -lt "$LAT_MIN" ] && LAT_MIN="$lat"
|
||||
[ "$lat" -gt "$LAT_MAX" ] && LAT_MAX="$lat"
|
||||
done
|
||||
echo " latency min=${LAT_MIN}ms max=${LAT_MAX}ms"
|
||||
fi
|
||||
else
|
||||
fail "S5 RAPID FIRE — cold start не прошёл, тест пропущен"
|
||||
fi
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# SECTION 6: ПАРАЛЛЕЛЬНЫЕ ВЫЗОВЫ — 5 параллельных invoke на каждый язык
|
||||
# Ищем: race conditions, конфликты при pod scheduling, deadlocks
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
echo ""
|
||||
echo "────────────────────────────────────────────────────────"
|
||||
echo " S6: ПАРАЛЛЕЛЬНЫЕ ВЫЗОВЫ — 5 одновременных invoke × 4 языка"
|
||||
echo "────────────────────────────────────────────────────────"
|
||||
PARALLEL_N=5
|
||||
|
||||
# Используем уже тёплые функции из S2 (CPU burn — уже invoke'd)
|
||||
declare -A PAR_FNS=( ["Python"]="$FN_CPU_PY" ["Node.js"]="$FN_CPU_NODE" ["Ruby"]="$FN_CPU_RUBY" ["PHP"]="$FN_CPU_PHP" )
|
||||
declare -A PAR_TAGS=( ["Python"]="cpu-burn-py" ["Node.js"]="cpu-burn-node" ["Ruby"]="cpu-burn-ruby" ["PHP"]="cpu-burn-php" )
|
||||
|
||||
for lang in "Python" "Node.js" "Ruby" "PHP"; do
|
||||
fn="${PAR_FNS[$lang]}"
|
||||
tag="${PAR_TAGS[$lang]}"
|
||||
echo ""
|
||||
echo ">>> S6 $lang: $PARALLEL_N параллельных invoke..."
|
||||
for i in $(seq 1 "$PARALLEL_N"); do
|
||||
(
|
||||
RESP=$(curl -s --max-time 60 -X POST "${BASE}/functions/${fn}/invoke" \
|
||||
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" -d '{}')
|
||||
STATUS=$(json_field "$RESP" status)
|
||||
RAW=$(json_field "$RESP" response_raw)
|
||||
if [ "$STATUS" = "200" ] && printf '%s' "$RAW" | grep -qF "$tag"; then
|
||||
echo "OK" > "${TMP}/par-${lang}-${i}.out"
|
||||
else
|
||||
echo "FAIL:status=${STATUS}" > "${TMP}/par-${lang}-${i}.out"
|
||||
fi
|
||||
) &
|
||||
done
|
||||
wait
|
||||
|
||||
PAR_OK=0; PAR_FAIL=0
|
||||
for i in $(seq 1 "$PARALLEL_N"); do
|
||||
result=$(cat "${TMP}/par-${lang}-${i}.out" 2>/dev/null || echo "FAIL:no-file")
|
||||
if [ "$result" = "OK" ]; then
|
||||
PAR_OK=$((PAR_OK+1))
|
||||
else
|
||||
PAR_FAIL=$((PAR_FAIL+1))
|
||||
echo " invoke $i: $result"
|
||||
fi
|
||||
done
|
||||
[ "$PAR_FAIL" -eq 0 ] \
|
||||
&& pass "S6 $lang PARALLEL: ${PARALLEL_N}/${PARALLEL_N} invoke → 200" \
|
||||
|| fail "S6 $lang PARALLEL: ${PAR_FAIL}/${PARALLEL_N} упали"
|
||||
done
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# SECTION 7: МЕДЛЕННЫЕ ФУНКЦИИ — sleep 6s, Python + Node.js
|
||||
# Ищем: router timeout, poolmgr idle eviction, 504 под нагрузкой
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
echo ""
|
||||
echo "────────────────────────────────────────────────────────"
|
||||
echo " S7: МЕДЛЕННЫЕ ФУНКЦИИ — sleep(6s) × Python + Node.js"
|
||||
echo "────────────────────────────────────────────────────────"
|
||||
|
||||
FN_SLOW_PY="t-slow-py-${RUN_ID}"
|
||||
cat > "${TMP}/slow_py.py" << 'PYEOF'
|
||||
import time
|
||||
def main(event, context):
|
||||
time.sleep(6)
|
||||
return "slow-py-ok", 200
|
||||
PYEOF
|
||||
create_json "python" "$FN_SLOW_PY" "${TMP}/slow_py.py"
|
||||
|
||||
FN_SLOW_NODE="t-slow-node-${RUN_ID}"
|
||||
cat > "${TMP}/slow_node.js" << 'JSEOF'
|
||||
module.exports = async function(ctx) {
|
||||
await new Promise(r => setTimeout(r, 6000));
|
||||
return { status: 200, body: "slow-node-ok" };
|
||||
};
|
||||
JSEOF
|
||||
create_json "nodejs" "$FN_SLOW_NODE" "${TMP}/slow_node.js"
|
||||
|
||||
echo ""
|
||||
echo ">>> S7: invoke (ждём cold start + 6s sleep)..."
|
||||
for fn_tag in \
|
||||
"${FN_SLOW_PY}:slow-py-ok:Python" \
|
||||
"${FN_SLOW_NODE}:slow-node-ok:Node.js"; do
|
||||
fn="${fn_tag%%:*}"; rest="${fn_tag#*:}"; tag="${rest%%:*}"; label="${rest#*:}"
|
||||
T_START=$(date +%s%3N)
|
||||
if wait_invoke "$SUB" "$fn" "$tag" 15 8; then
|
||||
T_END=$(date +%s%3N)
|
||||
LAT=$(( T_END - T_START ))
|
||||
pass "S7 $label SLOW(6s) → 200 за ~${LAT}ms total"
|
||||
# Повторный invoke (warm pod): должно занять ≥6s
|
||||
T2=$(date +%s%3N)
|
||||
RESP=$(curl -s --max-time 30 -X POST "${BASE}/functions/${fn}/invoke" \
|
||||
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" -d '{}')
|
||||
T2E=$(date +%s%3N)
|
||||
LAT2=$(( T2E - T2 ))
|
||||
STATUS=$(json_field "$RESP" status)
|
||||
RAW=$(json_field "$RESP" response_raw)
|
||||
echo " warm invoke: status=$STATUS latency=${LAT2}ms raw=$(printf '%s' "$RAW" | head -c 40)"
|
||||
if [ "$STATUS" = "200" ] && [ "$LAT2" -ge 5000 ]; then
|
||||
pass "S7 $label SLOW warm invoke → 200 latency=${LAT2}ms (≥5s)"
|
||||
elif [ "$STATUS" = "200" ]; then
|
||||
fail "S7 $label SLOW warm слишком быстро: ${LAT2}ms — sleep не сработал?"
|
||||
else
|
||||
fail "S7 $label SLOW warm invoke → status=$STATUS"
|
||||
fi
|
||||
else
|
||||
fail "S7 $label SLOW — cold start не прошёл"
|
||||
fi
|
||||
done
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# SECTION 8: ДАВЛЕНИЕ ПО ПАМЯТИ — аллокация 20MB, Python + Node.js
|
||||
# Ищем: OOM kill на поде, зависание, некорректный ответ
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
echo ""
|
||||
echo "────────────────────────────────────────────────────────"
|
||||
echo " S8: ПАМЯТЬ — аллокация 20MB (Python + Node.js)"
|
||||
echo "────────────────────────────────────────────────────────"
|
||||
|
||||
FN_MEM_PY="t-mem-py-${RUN_ID}"
|
||||
cat > "${TMP}/mem_py.py" << 'PYEOF'
|
||||
def main(event, context):
|
||||
# Аллоцируем 20MB и суммируем (чтобы не оптимизировалось)
|
||||
data = bytearray(20 * 1024 * 1024)
|
||||
for i in range(0, len(data), 4096):
|
||||
data[i] = i % 256
|
||||
checksum = sum(data[i] for i in range(0, len(data), 4096)) % 65536
|
||||
return "mem-py-ok:{}:{}".format(len(data), checksum), 200
|
||||
PYEOF
|
||||
create_json "python" "$FN_MEM_PY" "${TMP}/mem_py.py"
|
||||
|
||||
FN_MEM_NODE="t-mem-node-${RUN_ID}"
|
||||
cat > "${TMP}/mem_node.js" << 'JSEOF'
|
||||
module.exports = async function(ctx) {
|
||||
const size = 20 * 1024 * 1024;
|
||||
const buf = Buffer.alloc(size);
|
||||
for (let i = 0; i < size; i += 4096) buf[i] = i % 256;
|
||||
let checksum = 0;
|
||||
for (let i = 0; i < size; i += 4096) checksum = (checksum + buf[i]) % 65536;
|
||||
return { status: 200, body: `mem-node-ok:${size}:${checksum}` };
|
||||
};
|
||||
JSEOF
|
||||
create_json "nodejs" "$FN_MEM_NODE" "${TMP}/mem_node.js"
|
||||
|
||||
echo ""
|
||||
echo ">>> S8: invoke (20MB аллокация)..."
|
||||
for fn_tag in \
|
||||
"${FN_MEM_PY}:mem-py-ok:Python" \
|
||||
"${FN_MEM_NODE}:mem-node-ok:Node.js"; do
|
||||
fn="${fn_tag%%:*}"; rest="${fn_tag#*:}"; tag="${rest%%:*}"; label="${rest#*:}"
|
||||
if wait_invoke "$SUB" "$fn" "$tag" 18 6; then
|
||||
pass "S8 $label MEMORY(20MB) → 200"
|
||||
# Проверяем что данные в ответе правильные
|
||||
RESP=$(curl -s --max-time 30 -X POST "${BASE}/functions/${fn}/invoke" \
|
||||
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" -d '{}')
|
||||
RAW=$(json_field "$RESP" response_raw)
|
||||
if printf '%s' "$RAW" | grep -qE "$tag:[0-9]+:[0-9]+"; then
|
||||
pass "S8 $label MEMORY ответ содержит checksum: $(printf '%s' "$RAW" | head -c 60)"
|
||||
else
|
||||
fail "S8 $label MEMORY ответ некорректен: $(printf '%s' "$RAW" | head -c 60)"
|
||||
fi
|
||||
else
|
||||
fail "S8 $label MEMORY(20MB) — не вернулась (OOM kill?)"
|
||||
fi
|
||||
done
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
print_summary
|
||||
Reference in New Issue
Block a user