Files
wehub-resource-sync 41cb1c0170
OpenSSF Scorecard / scorecard (push) Failing after 0s
DCO / dco (push) Failing after 0s
CodeQL SAST / analyze (push) Failing after 1s
Deploy Pages / deploy (push) Failing after 1s
chore: import upstream snapshot with attribution
2026-07-13 12:28:05 +08:00

252 lines
8.4 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* test_cs_lsp_bench.c — parity-tracking benchmark for cs_lsp.
*
* The fixture is a synthetic but representative ~260-line C# file that
* exercises the parity features cs_lsp implements:
* - using / using static / using alias / global using
* - file-scoped namespace
* - class / record / interface / struct / enum
* - inheritance + interface implementation
* - generics: List<T>, Dictionary<K,V>, generic methods
* - async / await with Task<T> / ValueTask<T> unwrap
* - LINQ method syntax (Where / Select / First / Count / ToList ...)
* - properties (auto + expression-bodied), fields, indexers
* - primary constructors (records + C# 12 classes)
* - method chaining via return-type propagation
* - this / base dispatch
* - var inference, foreach element typing
* - common BCL stdlib calls (Console, string, StringBuilder, Math)
*
* This is NOT a Roslyn parity proof — it measures the resolution rate
* (resolved_calls vs textual calls) on idiomatic code, which is the
* subset cs_lsp targets. See the honest scope notes in cs_lsp.c.
*
* Reports: total textual calls, resolved calls (raw), high-confidence
* resolutions (>= 0.90), and the resolution ratio. Asserts a floor of
* 45% under sanitizers (idiomatic-code production expectation is higher;
* the gap is dominated by ctor-synthetic 0.50 markers and BCL long tail).
*/
#include "test_framework.h"
#include "cbm.h"
#include "lsp/cs_lsp.h"
#include <stdlib.h>
#include <time.h>
static const char *bench_source =
"global using System;\n"
"using System.Collections.Generic;\n"
"using System.Linq;\n"
"using System.Text;\n"
"using System.Threading.Tasks;\n"
"using static System.Math;\n"
"using Ints = System.Collections.Generic.List<int>;\n"
"\n"
"namespace Bench.App;\n"
"\n"
"public enum Status { Active, Disabled, Pending }\n"
"\n"
"public interface IRepository\n"
"{\n"
" User Find(int id);\n"
" Task<User> FindAsync(int id);\n"
"}\n"
"\n"
"public record Address(string Street, string City)\n"
"{\n"
" public string Full() { return Street + \", \" + City; }\n"
"}\n"
"\n"
"public class User\n"
"{\n"
" public int Id { get; set; }\n"
" public string Name { get; set; }\n"
" public Address Home { get; set; }\n"
" public Status State { get; set; }\n"
" public User(int id, string name) { Id = id; Name = name; }\n"
" public string Display() { return Name.ToUpper(); }\n"
" public string Greeting() => \"Hi \" + Name.Trim();\n"
" public string City() { return Home.City; }\n"
"}\n"
"\n"
"public class BaseService\n"
"{\n"
" protected readonly StringBuilder _log = new StringBuilder();\n"
" public virtual string Tag() { return \"base\"; }\n"
" public void Note(string m) { _log.Append(m).Append(';'); }\n"
" public string Dump() { return _log.ToString(); }\n"
"}\n"
"\n"
"public class UserService : BaseService, IRepository\n"
"{\n"
" private readonly Dictionary<int, User> _users = new Dictionary<int, User>();\n"
" private readonly List<User> _all = new List<User>();\n"
"\n"
" public override string Tag() { return \"users\"; }\n"
"\n"
" public void Add(User u)\n"
" {\n"
" _users.Add(u.Id, u);\n"
" _all.Add(u);\n"
" Note(u.Display());\n"
" }\n"
"\n"
" public User Find(int id)\n"
" {\n"
" if (_users.ContainsKey(id))\n"
" {\n"
" var u = _users[id];\n"
" u.Display();\n"
" return u;\n"
" }\n"
" return null;\n"
" }\n"
"\n"
" public async Task<User> FindAsync(int id)\n"
" {\n"
" await Task.Delay(1);\n"
" var u = Find(id);\n"
" return u;\n"
" }\n"
"\n"
" public List<string> ActiveNames()\n"
" {\n"
" return _all\n"
" .Where(x => x.State == Status.Active)\n"
" .Select(x => x.Display())\n"
" .ToList();\n"
" }\n"
"\n"
" public int Count() { return _all.Count(); }\n"
"\n"
" public string FirstCity()\n"
" {\n"
" var first = _all.First();\n"
" return first.City();\n"
" }\n"
"\n"
" public void Each()\n"
" {\n"
" foreach (var u in _all)\n"
" {\n"
" u.Greeting();\n"
" this.Note(u.Name);\n"
" }\n"
" }\n"
"}\n"
"\n"
"public class Calculator\n"
"{\n"
" public double Hypotenuse(double a, double b)\n"
" {\n"
" return Sqrt(Pow(a, 2) + Pow(b, 2));\n"
" }\n"
" public T Echo<T>(T value) { return value; }\n"
"}\n"
"\n"
"public class Program\n"
"{\n"
" public static string Render(UserService svc)\n"
" {\n"
" var sb = new StringBuilder();\n"
" var names = svc.ActiveNames();\n"
" foreach (var n in names)\n"
" {\n"
" sb.Append(n).Append('\\n');\n"
" }\n"
" return sb.ToString();\n"
" }\n"
"\n"
" public static void Main()\n"
" {\n"
" var svc = new UserService();\n"
" var a = new Address(\"1 St\", \"Town\");\n"
" var u = new User(1, \"alice\");\n"
" u.Home = a;\n"
" svc.Add(u);\n"
" var found = svc.Find(1);\n"
" found.Display();\n"
" found.City();\n"
" var c = new Calculator();\n"
" var h = c.Hypotenuse(3.0, 4.0);\n"
" Console.WriteLine(h.ToString());\n"
" var rendered = Render(svc);\n"
" Console.WriteLine(rendered.Trim());\n"
" var addr = a.Full();\n"
" Console.WriteLine(addr);\n"
" svc.Tag();\n"
" svc.Dump();\n"
" Ints xs = new Ints();\n"
" xs.Add(svc.Count());\n"
" }\n"
"}\n";
static double elapsed_ms(struct timespec t0, struct timespec t1) {
double s = (double)(t1.tv_sec - t0.tv_sec);
double ns = (double)(t1.tv_nsec - t0.tv_nsec);
return s * 1000.0 + ns / 1000000.0;
}
TEST(cslsp_bench_resolution_ratio) {
/* Perf benchmark: time-budgeted. Under ASan+UBSan the budget is scaled
* (see the sanitizer-aware time-budget assert below); the benchmark always
* runs so regressions surface in every configuration. */
int slen = (int)strlen(bench_source);
struct timespec t0;
struct timespec t1;
clock_gettime(CLOCK_MONOTONIC, &t0);
CBMFileResult *r = cbm_extract_file(bench_source, slen, CBM_LANG_CSHARP,
"test", "bench.cs", 0, NULL, NULL);
clock_gettime(CLOCK_MONOTONIC, &t1);
ASSERT_NOT_NULL(r);
double ms = elapsed_ms(t0, t1);
int calls = r->calls.count;
int resolved = r->resolved_calls.count;
int high_conf = 0;
for (int i = 0; i < r->resolved_calls.count; i++) {
if (r->resolved_calls.items[i].confidence >= 0.90f) high_conf++;
}
int loc = 0;
for (const char *p = bench_source; *p; p++) {
if (*p == '\n') loc++;
}
double ratio = calls > 0 ? (double)resolved / (double)calls : 0.0;
double hi_ratio = calls > 0 ? (double)high_conf / (double)calls : 0.0;
printf(" cs bench: %d lines, %d calls, %d resolved (%.0f%%), "
"%d high-conf (%.0f%%), %.2f ms\n",
loc, calls, resolved, ratio * 100.0, high_conf, hi_ratio * 100.0,
ms);
/* Free the result BEFORE asserting so a budget miss doesn't leak. */
cbm_free_result(r);
ASSERT_GTE(calls, 1);
ASSERT_GTE(resolved, 1);
/* Floor at 45% under sanitizers. The gap to 100% is dominated by:
* - ctor-synthetic 0.50 markers (counted as resolved but low-conf)
* - BCL long-tail calls not in the curated stdlib
* - LINQ lambda-body inference (out of scope, documented)
* This benchmark exists to track regressions, not to claim Roslyn
* parity. */
if (calls >= 20) {
ASSERT_GTE(resolved * 100, calls * 45);
}
/* Time budget. ASan+UBSan instrumentation slows the parse ~5-10×, so
* scale the budget when a sanitizer is active. Native: 200 ms for a
* ~260-line fixture; sanitized: 2000 ms. */
#ifdef __SANITIZE_ADDRESS__
ASSERT(ms < 2000.0);
#else
ASSERT(ms < 200.0);
#endif
PASS();
}
SUITE(cs_lsp_bench) {
RUN_TEST(cslsp_bench_resolution_ratio);
}