import subprocess, tempfile, os, re, sys def extract_default(content, start): """Extract the :default branch value from reader conditional content. Returns (value_string, consumed_length).""" content = content.strip() # Find :default keyword default_idx = content.find(':default') if default_idx < 0: return '', len(content) rest = content[default_idx + len(':default'):].strip() if not rest: return '', len(content) # Read one s-expression if rest[0] in '([': open_c = rest[0] close_c = ']' if open_c == '[' else ')' depth = 0 k = 0 while k < len(rest): if rest[k] == open_c: depth += 1 elif rest[k] == close_c: depth -= 1 if depth == 0: return rest[:k+1], k+1 k += 1 return rest, len(rest) elif rest[0] == '"': # String k = 1 while k < len(rest): if rest[k] == '\\': k += 1 elif rest[k] == '"': return rest[:k+1], k+1 k += 1 return rest, len(rest) else: # Atom: read until whitespace or ) k = 0 while k < len(rest) and rest[k] not in ' \t\n\r,)': k += 1 return rest[:k], k def strip_reader_conditionals(text): """Strip #? and #?@ reader conditionals, keeping :default branch content.""" result = [] i = 0 while i < len(text): # Check for #?@ or #? if i + 3 <= len(text) and text[i:i+3] == '#?@': j = i + 3 elif i + 2 <= len(text) and text[i:i+2] == '#?': j = i + 2 else: result.append(text[i]) i += 1 continue # Skip whitespace between #? and ( while j < len(text) and text[j] in ' \t\n\r': j += 1 if j >= len(text) or text[j] != '(': result.append(text[i]) i += 1 continue # Find matching closing paren depth = 0 k = j while k < len(text): if text[k] == '(': depth += 1 elif text[k] == ')': depth -= 1 if depth == 0: break k += 1 if depth != 0: result.append(text[i]) i += 1 continue # Extract content between parens inner = text[j+1:k] default_val, _ = extract_default(inner, 0) result.append(default_val) i = k + 1 return ''.join(result) def run_test(cljc_path, timeout=30): with open(cljc_path, 'r') as f: content = f.read() content = strip_reader_conditionals(content) content = content.replace('##Inf', 'inf').replace('##-Inf', '-inf').replace('##NaN', 'nan') content = re.sub(r'#\{[^}]*\}', r'@[]', content) # #{} → @[] content = re.sub(r'#:([\w-]+)', r'\1', content) # #:foo → foo stubs = '''(do (defmacro is [form] (list 'quote form)) (defmacro testing [desc & body] (cons 'do body)) (defmacro thrown? [form] (list 'try form false (list 'catch 'Exception 'e true))) (defmacro deftest [name & body] (list 'def name (cons 'fn (cons [] body)))) ''' content = stubs + content + ')' content = re.sub(r'clojure\.test', 'test', content) fd, path = tempfile.mkstemp(suffix='.clj') try: os.write(fd, content.encode()) finally: os.close(fd) try: result = subprocess.run(['./cljnim', 'run', path], capture_output=True, text=True, timeout=timeout) return result.returncode, result.stdout, result.stderr finally: os.unlink(path) if len(sys.argv) > 1: cljc = sys.argv[1] rc, out, err = run_test(cljc) print(f'rc={rc}') if rc != 0: print('stderr:', err[:500]) if out.strip(): print('stdout:', out[:500]) else: for test in ['zipmap', 'zero_qmark', 'with_out_str']: cljc = f'/tmp/clojure-test-suite/test/clojure/core_test/{test}.cljc' rc, out, err = run_test(cljc) print(f'{test}: rc={rc}') if rc != 0: print(' stderr:', err[:300])