mcp-garry-codes 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +133 -0
- package/dist/config/env.d.ts +4 -0
- package/dist/config/env.d.ts.map +1 -0
- package/dist/config/env.js +27 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/schema.d.ts +28 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +12 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/contracts/common.d.ts +53 -0
- package/dist/contracts/common.d.ts.map +1 -0
- package/dist/contracts/common.js +30 -0
- package/dist/contracts/common.js.map +1 -0
- package/dist/contracts/linter_contracts.d.ts +79 -0
- package/dist/contracts/linter_contracts.d.ts.map +1 -0
- package/dist/contracts/linter_contracts.js +16 -0
- package/dist/contracts/linter_contracts.js.map +1 -0
- package/dist/contracts/scaffold_contracts.d.ts +84 -0
- package/dist/contracts/scaffold_contracts.d.ts.map +1 -0
- package/dist/contracts/scaffold_contracts.js +27 -0
- package/dist/contracts/scaffold_contracts.js.map +1 -0
- package/dist/contracts/security_contracts.d.ts +37 -0
- package/dist/contracts/security_contracts.d.ts.map +1 -0
- package/dist/contracts/security_contracts.js +6 -0
- package/dist/contracts/security_contracts.js.map +1 -0
- package/dist/contracts/test_contracts.d.ts +42 -0
- package/dist/contracts/test_contracts.d.ts.map +1 -0
- package/dist/contracts/test_contracts.js +10 -0
- package/dist/contracts/test_contracts.js.map +1 -0
- package/dist/contracts/wiki_contracts.d.ts +96 -0
- package/dist/contracts/wiki_contracts.d.ts.map +1 -0
- package/dist/contracts/wiki_contracts.js +17 -0
- package/dist/contracts/wiki_contracts.js.map +1 -0
- package/dist/core/cache.d.ts +9 -0
- package/dist/core/cache.d.ts.map +1 -0
- package/dist/core/cache.js +56 -0
- package/dist/core/cache.js.map +1 -0
- package/dist/core/errors.d.ts +37 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +52 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/file_utils.d.ts +13 -0
- package/dist/core/file_utils.d.ts.map +1 -0
- package/dist/core/file_utils.js +90 -0
- package/dist/core/file_utils.js.map +1 -0
- package/dist/core/logger.d.ts +18 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +46 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/process_runner.d.ts +15 -0
- package/dist/core/process_runner.d.ts.map +1 -0
- package/dist/core/process_runner.js +64 -0
- package/dist/core/process_runner.js.map +1 -0
- package/dist/domain/glua/ast.d.ts +12 -0
- package/dist/domain/glua/ast.d.ts.map +1 -0
- package/dist/domain/glua/ast.js +176 -0
- package/dist/domain/glua/ast.js.map +1 -0
- package/dist/domain/glua/lexer.d.ts +42 -0
- package/dist/domain/glua/lexer.d.ts.map +1 -0
- package/dist/domain/glua/lexer.js +232 -0
- package/dist/domain/glua/lexer.js.map +1 -0
- package/dist/domain/networking/net_auditor.d.ts +5 -0
- package/dist/domain/networking/net_auditor.d.ts.map +1 -0
- package/dist/domain/networking/net_auditor.js +188 -0
- package/dist/domain/networking/net_auditor.js.map +1 -0
- package/dist/domain/packaging/validator.d.ts +5 -0
- package/dist/domain/packaging/validator.d.ts.map +1 -0
- package/dist/domain/packaging/validator.js +121 -0
- package/dist/domain/packaging/validator.js.map +1 -0
- package/dist/domain/performance/hot_path_analyzer.d.ts +5 -0
- package/dist/domain/performance/hot_path_analyzer.d.ts.map +1 -0
- package/dist/domain/performance/hot_path_analyzer.js +127 -0
- package/dist/domain/performance/hot_path_analyzer.js.map +1 -0
- package/dist/domain/scaffolding/templates.d.ts +5 -0
- package/dist/domain/scaffolding/templates.d.ts.map +1 -0
- package/dist/domain/scaffolding/templates.js +219 -0
- package/dist/domain/scaffolding/templates.js.map +1 -0
- package/dist/domain/wiki/offline_db.d.ts +3 -0
- package/dist/domain/wiki/offline_db.d.ts.map +1 -0
- package/dist/domain/wiki/offline_db.js +534 -0
- package/dist/domain/wiki/offline_db.js.map +1 -0
- package/dist/domain/wiki/types.d.ts +29 -0
- package/dist/domain/wiki/types.d.ts.map +1 -0
- package/dist/domain/wiki/types.js +2 -0
- package/dist/domain/wiki/types.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/glualint_adapter.d.ts +6 -0
- package/dist/integrations/glualint_adapter.d.ts.map +1 -0
- package/dist/integrations/glualint_adapter.js +62 -0
- package/dist/integrations/glualint_adapter.js.map +1 -0
- package/dist/integrations/python_test_harness.d.ts +5 -0
- package/dist/integrations/python_test_harness.d.ts.map +1 -0
- package/dist/integrations/python_test_harness.js +67 -0
- package/dist/integrations/python_test_harness.js.map +1 -0
- package/dist/integrations/web_search_adapter.d.ts +6 -0
- package/dist/integrations/web_search_adapter.d.ts.map +1 -0
- package/dist/integrations/web_search_adapter.js +80 -0
- package/dist/integrations/web_search_adapter.js.map +1 -0
- package/dist/integrations/wiki_client.d.ts +13 -0
- package/dist/integrations/wiki_client.d.ts.map +1 -0
- package/dist/integrations/wiki_client.js +110 -0
- package/dist/integrations/wiki_client.js.map +1 -0
- package/dist/prompts/index.d.ts +73 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +127 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/resources/realms_resources.d.ts +2 -0
- package/dist/resources/realms_resources.d.ts.map +1 -0
- package/dist/resources/realms_resources.js +30 -0
- package/dist/resources/realms_resources.js.map +1 -0
- package/dist/resources/schema_resources.d.ts +2 -0
- package/dist/resources/schema_resources.d.ts.map +1 -0
- package/dist/resources/schema_resources.js +43 -0
- package/dist/resources/schema_resources.js.map +1 -0
- package/dist/resources/wiki_resources.d.ts +3 -0
- package/dist/resources/wiki_resources.d.ts.map +1 -0
- package/dist/resources/wiki_resources.js +31 -0
- package/dist/resources/wiki_resources.js.map +1 -0
- package/dist/security/path_guard.d.ts +8 -0
- package/dist/security/path_guard.d.ts.map +1 -0
- package/dist/security/path_guard.js +37 -0
- package/dist/security/path_guard.js.map +1 -0
- package/dist/security/process_guard.d.ts +8 -0
- package/dist/security/process_guard.d.ts.map +1 -0
- package/dist/security/process_guard.js +26 -0
- package/dist/security/process_guard.js.map +1 -0
- package/dist/security/redaction.d.ts +6 -0
- package/dist/security/redaction.d.ts.map +1 -0
- package/dist/security/redaction.js +49 -0
- package/dist/security/redaction.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +364 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/analyze_performance.d.ts +4 -0
- package/dist/tools/analyze_performance.d.ts.map +1 -0
- package/dist/tools/analyze_performance.js +20 -0
- package/dist/tools/analyze_performance.js.map +1 -0
- package/dist/tools/audit_net_security.d.ts +4 -0
- package/dist/tools/audit_net_security.d.ts.map +1 -0
- package/dist/tools/audit_net_security.js +19 -0
- package/dist/tools/audit_net_security.js.map +1 -0
- package/dist/tools/get_gmod_symbol.d.ts +4 -0
- package/dist/tools/get_gmod_symbol.d.ts.map +1 -0
- package/dist/tools/get_gmod_symbol.js +30 -0
- package/dist/tools/get_gmod_symbol.js.map +1 -0
- package/dist/tools/lint_glua.d.ts +4 -0
- package/dist/tools/lint_glua.d.ts.map +1 -0
- package/dist/tools/lint_glua.js +24 -0
- package/dist/tools/lint_glua.js.map +1 -0
- package/dist/tools/package_and_validate.d.ts +4 -0
- package/dist/tools/package_and_validate.d.ts.map +1 -0
- package/dist/tools/package_and_validate.js +23 -0
- package/dist/tools/package_and_validate.js.map +1 -0
- package/dist/tools/run_glua_test.d.ts +4 -0
- package/dist/tools/run_glua_test.d.ts.map +1 -0
- package/dist/tools/run_glua_test.js +15 -0
- package/dist/tools/run_glua_test.js.map +1 -0
- package/dist/tools/scaffold_gmod_component.d.ts +4 -0
- package/dist/tools/scaffold_gmod_component.d.ts.map +1 -0
- package/dist/tools/scaffold_gmod_component.js +16 -0
- package/dist/tools/scaffold_gmod_component.js.map +1 -0
- package/dist/tools/search_gmod_wiki.d.ts +4 -0
- package/dist/tools/search_gmod_wiki.d.ts.map +1 -0
- package/dist/tools/search_gmod_wiki.js +30 -0
- package/dist/tools/search_gmod_wiki.js.map +1 -0
- package/dist/tools/web_search_glua.d.ts +4 -0
- package/dist/tools/web_search_glua.d.ts.map +1 -0
- package/dist/tools/web_search_glua.js +15 -0
- package/dist/tools/web_search_glua.js.map +1 -0
- package/package.json +58 -0
- package/src/test_harness/lua/glua_mock.lua +383 -0
- package/src/test_harness/python/gmod_tester.py +156 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
--[[
|
|
2
|
+
glua_mock.lua — Embedded Source Engine Headless Runtime Mock
|
|
3
|
+
Simulates Garry's Mod core globals, Vector, Angle, Color, hook, and net libraries in standalone Lua.
|
|
4
|
+
]]
|
|
5
|
+
|
|
6
|
+
_G.SERVER = true
|
|
7
|
+
_G.CLIENT = false
|
|
8
|
+
_G.SHARED = true
|
|
9
|
+
|
|
10
|
+
-- Time & Screen mocks
|
|
11
|
+
local _curTime = 100.0
|
|
12
|
+
function _G.CurTime() return _curTime end
|
|
13
|
+
function _G.FrameTime() return 1 / 66.66 end
|
|
14
|
+
function _G.ScrW() return 1920 end
|
|
15
|
+
function _G.ScrH() return 1080 end
|
|
16
|
+
|
|
17
|
+
-- Color Mock
|
|
18
|
+
function _G.Color(r, g, b, a)
|
|
19
|
+
local col = { r = r or 255, g = g or 255, b = b or 255, a = a or 255 }
|
|
20
|
+
setmetatable(col, {
|
|
21
|
+
__tostring = function(t) return string.format("Color(%d, %d, %d, %d)", t.r, t.g, t.b, t.a) end
|
|
22
|
+
})
|
|
23
|
+
return col
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
-- Vector Mock
|
|
27
|
+
local VectorMeta = {}
|
|
28
|
+
VectorMeta.__index = VectorMeta
|
|
29
|
+
|
|
30
|
+
function VectorMeta:Length()
|
|
31
|
+
return math.sqrt(self.x * self.x + self.y * self.y + self.z * self.z)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
function VectorMeta:Distance(other)
|
|
35
|
+
local dx = self.x - other.x
|
|
36
|
+
local dy = self.y - other.y
|
|
37
|
+
local dz = self.z - other.z
|
|
38
|
+
return math.sqrt(dx * dx + dy * dy + dz * dz)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
function VectorMeta:Dot(other)
|
|
42
|
+
return self.x * other.x + self.y * other.y + self.z * other.z
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
function VectorMeta:Cross(other)
|
|
46
|
+
return _G.Vector(
|
|
47
|
+
self.y * other.z - self.z * other.y,
|
|
48
|
+
self.z * other.x - self.x * other.z,
|
|
49
|
+
self.x * other.y - self.y * other.x
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
function VectorMeta.__add(a, b)
|
|
54
|
+
return _G.Vector(a.x + b.x, a.y + b.y, a.z + b.z)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
function VectorMeta.__sub(a, b)
|
|
58
|
+
return _G.Vector(a.x - b.x, a.y - b.y, a.z - b.z)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
function VectorMeta.__mul(a, b)
|
|
62
|
+
if type(b) == "number" then
|
|
63
|
+
return _G.Vector(a.x * b, a.y * b, a.z * b)
|
|
64
|
+
elseif type(a) == "number" then
|
|
65
|
+
return _G.Vector(b.x * a, b.y * a, b.z * a)
|
|
66
|
+
end
|
|
67
|
+
return _G.Vector(a.x * b.x, a.y * b.y, a.z * b.z)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
function VectorMeta.__tostring(v)
|
|
71
|
+
return string.format("Vector(%f, %f, %f)", v.x, v.y, v.z)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
function _G.Vector(x, y, z)
|
|
75
|
+
local v = { x = tonumber(x) or 0, y = tonumber(y) or 0, z = tonumber(z) or 0 }
|
|
76
|
+
setmetatable(v, VectorMeta)
|
|
77
|
+
return v
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
-- Angle Mock
|
|
81
|
+
local AngleMeta = {}
|
|
82
|
+
AngleMeta.__index = AngleMeta
|
|
83
|
+
function AngleMeta:Forward() return _G.Vector(1, 0, 0) end
|
|
84
|
+
function AngleMeta:Right() return _G.Vector(0, 1, 0) end
|
|
85
|
+
function AngleMeta:Up() return _G.Vector(0, 0, 1) end
|
|
86
|
+
|
|
87
|
+
function _G.Angle(p, y, r)
|
|
88
|
+
local a = { p = tonumber(p) or 0, y = tonumber(y) or 0, r = tonumber(r) or 0 }
|
|
89
|
+
setmetatable(a, AngleMeta)
|
|
90
|
+
return a
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
-- Hook Library Mock
|
|
94
|
+
_G.hook = {
|
|
95
|
+
_hooks = {}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function _G.hook.Add(event, id, callback)
|
|
99
|
+
_G.hook._hooks[event] = _G.hook._hooks[event] or {}
|
|
100
|
+
_G.hook._hooks[event][id] = callback
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
function _G.hook.Remove(event, id)
|
|
104
|
+
if _G.hook._hooks[event] then
|
|
105
|
+
_G.hook._hooks[event][id] = nil
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
function _G.hook.Run(event, ...)
|
|
110
|
+
local listeners = _G.hook._hooks[event]
|
|
111
|
+
if not listeners then return end
|
|
112
|
+
for id, func in pairs(listeners) do
|
|
113
|
+
local a, b, c, d, e = func(...)
|
|
114
|
+
if a ~= nil then
|
|
115
|
+
return a, b, c, d, e
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
function VectorMeta:DistToSqr(other)
|
|
121
|
+
local dx = self.x - other.x
|
|
122
|
+
local dy = self.y - other.y
|
|
123
|
+
local dz = self.z - other.z
|
|
124
|
+
return dx * dx + dy * dy + dz * dz
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
function VectorMeta:Length2D()
|
|
128
|
+
return math.sqrt(self.x * self.x + self.y * self.y)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
function VectorMeta:Normalize()
|
|
132
|
+
local len = self:Length()
|
|
133
|
+
if len > 0 then
|
|
134
|
+
self.x = self.x / len
|
|
135
|
+
self.y = self.y / len
|
|
136
|
+
self.z = self.z / len
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
-- Angle Mock Extensions
|
|
141
|
+
function AngleMeta:RotateAroundAxis(axis, degrees)
|
|
142
|
+
-- Simplified mock rotation
|
|
143
|
+
return self
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
function AngleMeta:Normalize()
|
|
147
|
+
self.p = (self.p + 180) % 360 - 180
|
|
148
|
+
self.y = (self.y + 180) % 360 - 180
|
|
149
|
+
self.r = (self.r + 180) % 360 - 180
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
-- Entity & Player Mocks
|
|
153
|
+
local EntityMeta = {}
|
|
154
|
+
EntityMeta.__index = EntityMeta
|
|
155
|
+
|
|
156
|
+
function EntityMeta:IsValid() return self._valid ~= false end
|
|
157
|
+
function EntityMeta:GetPos() return self._pos end
|
|
158
|
+
function EntityMeta:SetPos(pos) self._pos = pos end
|
|
159
|
+
function EntityMeta:GetAngles() return self._ang or _G.Angle(0, 0, 0) end
|
|
160
|
+
function EntityMeta:SetAngles(ang) self._ang = ang end
|
|
161
|
+
function EntityMeta:GetClass() return self._class or "prop_physics" end
|
|
162
|
+
function EntityMeta:Health() return self._health end
|
|
163
|
+
function EntityMeta:SetHealth(hp) self._health = hp end
|
|
164
|
+
function EntityMeta:IsPlayer() return self._isPlayer == true end
|
|
165
|
+
function EntityMeta:IsAdmin() return self._isAdmin == true end
|
|
166
|
+
function EntityMeta:IsSuperAdmin() return self._isSuperAdmin == true end
|
|
167
|
+
function EntityMeta:Nick() return self._name or "Player" end
|
|
168
|
+
function EntityMeta:SteamID() return self._steamid or "STEAM_0:1:12345678" end
|
|
169
|
+
function EntityMeta:ChatPrint(msg) table.insert(self._chat, msg) end
|
|
170
|
+
function EntityMeta:EyePos() return self._pos + _G.Vector(0, 0, 64) end
|
|
171
|
+
function EntityMeta:GetAimVector() return _G.Vector(1, 0, 0) end
|
|
172
|
+
function EntityMeta:Alive() return (self._health or 100) > 0 end
|
|
173
|
+
function EntityMeta:Armor() return self._armor or 100 end
|
|
174
|
+
function EntityMeta:SetArmor(a) self._armor = a end
|
|
175
|
+
function EntityMeta:KeyDown(key) return false end
|
|
176
|
+
function EntityMeta:GetEyeTrace()
|
|
177
|
+
return _G.util.TraceLine({ start = self:EyePos(), endpos = self:EyePos() + self:GetAimVector() * 2048, filter = self })
|
|
178
|
+
end
|
|
179
|
+
function EntityMeta:Remove() self._valid = false end
|
|
180
|
+
|
|
181
|
+
function _G.IsValid(ent)
|
|
182
|
+
return ent ~= nil and type(ent) == "table" and ent.IsValid and ent:IsValid()
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
function _G.Entity(id)
|
|
186
|
+
local ent = {
|
|
187
|
+
_id = id or 1,
|
|
188
|
+
_valid = true,
|
|
189
|
+
_pos = _G.Vector(0, 0, 0),
|
|
190
|
+
_health = 100,
|
|
191
|
+
_isPlayer = false,
|
|
192
|
+
_chat = {}
|
|
193
|
+
}
|
|
194
|
+
setmetatable(ent, EntityMeta)
|
|
195
|
+
return ent
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
local _mockPlayer = _G.Entity(1)
|
|
199
|
+
_mockPlayer._isPlayer = true
|
|
200
|
+
_mockPlayer._isAdmin = true
|
|
201
|
+
_mockPlayer._name = "M A R W A N"
|
|
202
|
+
|
|
203
|
+
function _G.Player(id) return _mockPlayer end
|
|
204
|
+
function _G.LocalPlayer() return _mockPlayer end
|
|
205
|
+
|
|
206
|
+
-- Net Library Simulation (In-Memory Bitstream FIFO Buffer)
|
|
207
|
+
_G.net = {
|
|
208
|
+
_activeMessage = nil,
|
|
209
|
+
_buffer = {},
|
|
210
|
+
_receivers = {},
|
|
211
|
+
_registeredStrings = {}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function _G.util = {
|
|
215
|
+
AddNetworkString = function(str)
|
|
216
|
+
_G.net._registeredStrings[str] = true
|
|
217
|
+
return 1
|
|
218
|
+
end
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function _G.net.Start(name, unreliable)
|
|
222
|
+
_G.net._activeMessage = {
|
|
223
|
+
name = name,
|
|
224
|
+
unreliable = unreliable or false,
|
|
225
|
+
stream = {},
|
|
226
|
+
readIdx = 1
|
|
227
|
+
}
|
|
228
|
+
return true
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
function _G.net.WriteString(val)
|
|
232
|
+
table.insert(_G.net._activeMessage.stream, { type = "string", val = tostring(val) })
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
function _G.net.ReadString()
|
|
236
|
+
local item = _G.net._activeMessage.stream[_G.net._activeMessage.readIdx]
|
|
237
|
+
_G.net._activeMessage.readIdx = _G.net._activeMessage.readIdx + 1
|
|
238
|
+
return item and item.val or ""
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
function _G.net.WriteUInt(val, bits)
|
|
242
|
+
table.insert(_G.net._activeMessage.stream, { type = "uint", val = tonumber(val), bits = bits })
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
function _G.net.ReadUInt(bits)
|
|
246
|
+
local item = _G.net._activeMessage.stream[_G.net._activeMessage.readIdx]
|
|
247
|
+
_G.net._activeMessage.readIdx = _G.net._activeMessage.readIdx + 1
|
|
248
|
+
return item and item.val or 0
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
function _G.net.WriteBool(val)
|
|
252
|
+
table.insert(_G.net._activeMessage.stream, { type = "bool", val = val == true })
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
function _G.net.ReadBool()
|
|
256
|
+
local item = _G.net._activeMessage.stream[_G.net._activeMessage.readIdx]
|
|
257
|
+
_G.net._activeMessage.readIdx = _G.net._activeMessage.readIdx + 1
|
|
258
|
+
return item and item.val or false
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
function _G.net.Receive(name, callback)
|
|
262
|
+
_G.net._receivers[name] = callback
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
function _G.net.SendToServer()
|
|
266
|
+
local msg = _G.net._activeMessage
|
|
267
|
+
local receiver = _G.net._receivers[msg.name]
|
|
268
|
+
if receiver then
|
|
269
|
+
msg.readIdx = 1
|
|
270
|
+
receiver(#msg.stream, _mockPlayer)
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
function _G.net.Send(target)
|
|
275
|
+
local msg = _G.net._activeMessage
|
|
276
|
+
local receiver = _G.net._receivers[msg.name]
|
|
277
|
+
if receiver then
|
|
278
|
+
msg.readIdx = 1
|
|
279
|
+
receiver(#msg.stream, target)
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
function _G.net.Broadcast()
|
|
284
|
+
local msg = _G.net._activeMessage
|
|
285
|
+
local receiver = _G.net._receivers[msg.name]
|
|
286
|
+
if receiver then
|
|
287
|
+
msg.readIdx = 1
|
|
288
|
+
receiver(#msg.stream)
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
-- Timer Library Mock
|
|
293
|
+
_G.timer = {
|
|
294
|
+
_timers = {}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function _G.timer.Simple(delay, func)
|
|
298
|
+
func()
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
function _G.timer.Create(id, delay, reps, func)
|
|
302
|
+
_G.timer._timers[id] = { delay = delay, reps = reps, func = func }
|
|
303
|
+
func()
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
function _G.timer.Remove(id)
|
|
307
|
+
_G.timer._timers[id] = nil
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
function _G.timer.Exists(id)
|
|
311
|
+
return _G.timer._timers[id] ~= nil
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
-- Util TraceLine Mock
|
|
315
|
+
function _G.util.TraceLine(traceData)
|
|
316
|
+
local startPos = traceData.start or _G.Vector(0, 0, 0)
|
|
317
|
+
local endPos = traceData.endpos or _G.Vector(0, 0, 0)
|
|
318
|
+
return {
|
|
319
|
+
Hit = true,
|
|
320
|
+
HitPos = endPos,
|
|
321
|
+
HitNormal = _G.Vector(0, 0, 1),
|
|
322
|
+
Entity = _G.Entity(0),
|
|
323
|
+
Fraction = 1.0,
|
|
324
|
+
StartPos = startPos
|
|
325
|
+
}
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
-- Cam & Render Mocks
|
|
329
|
+
_G.cam = {
|
|
330
|
+
Start3D2D = function(pos, ang, scale) end,
|
|
331
|
+
End3D2D = function() end
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
_G.render = {
|
|
335
|
+
SetColorMaterial = function() end,
|
|
336
|
+
DrawBox = function(pos, ang, min, max, col) end
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
-- Draw & Surface Mocks
|
|
340
|
+
_G.surface = {
|
|
341
|
+
SetDrawColor = function(r, g, b, a) end,
|
|
342
|
+
DrawRect = function(x, y, w, h) end,
|
|
343
|
+
SetMaterial = function(mat) end,
|
|
344
|
+
CreateFont = function(name, data) end
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
_G.draw = {
|
|
348
|
+
SimpleText = function(text, font, x, y, col, xAlign, yAlign) end,
|
|
349
|
+
RoundedBox = function(rad, x, y, w, h, col) end
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
-- Test Assertion Helpers
|
|
353
|
+
_G.TEST_RESULTS = {
|
|
354
|
+
passed = 0,
|
|
355
|
+
failed = 0,
|
|
356
|
+
errors = {}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function _G.assert_equal(expected, actual, desc)
|
|
360
|
+
desc = desc or "Equality check"
|
|
361
|
+
if expected == actual then
|
|
362
|
+
_G.TEST_RESULTS.passed = _G.TEST_RESULTS.passed + 1
|
|
363
|
+
else
|
|
364
|
+
_G.TEST_RESULTS.failed = _G.TEST_RESULTS.failed + 1
|
|
365
|
+
table.insert(_G.TEST_RESULTS.errors, {
|
|
366
|
+
test_name = desc,
|
|
367
|
+
error_message = string.format("Assertion failed: expected '%s', got '%s'", tostring(expected), tostring(actual)),
|
|
368
|
+
stack_trace = debug.traceback()
|
|
369
|
+
})
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
function _G.assert_true(val, desc)
|
|
374
|
+
_G.assert_equal(true, val == true, desc or "Truth check")
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
function _G.print_test_summary()
|
|
378
|
+
print(string.format("=== TESTS RUN: %d | PASSED: %d | FAILED: %d ===",
|
|
379
|
+
_G.TEST_RESULTS.passed + _G.TEST_RESULTS.failed,
|
|
380
|
+
_G.TEST_RESULTS.passed,
|
|
381
|
+
_G.TEST_RESULTS.failed
|
|
382
|
+
))
|
|
383
|
+
end
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""
|
|
2
|
+
gmod_tester.py — Python GLua Headless Test Orchestrator
|
|
3
|
+
Executes unit tests against the Source Engine mock runtime and emits structured JSON test results.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
import json
|
|
8
|
+
import time
|
|
9
|
+
import os
|
|
10
|
+
import subprocess
|
|
11
|
+
import shutil
|
|
12
|
+
|
|
13
|
+
def run_test(test_file_path=None, inline_code=None, mock_realm="shared", timeout_sec=5):
|
|
14
|
+
start_time = time.time()
|
|
15
|
+
errors = []
|
|
16
|
+
output_log = []
|
|
17
|
+
tests_run = 0
|
|
18
|
+
failures = 0
|
|
19
|
+
|
|
20
|
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
21
|
+
lua_mock_path = os.path.join(script_dir, "..", "lua", "glua_mock.lua")
|
|
22
|
+
|
|
23
|
+
# Check for lua / luajit executable
|
|
24
|
+
lua_bin = shutil.which("luajit") or shutil.which("lua")
|
|
25
|
+
|
|
26
|
+
# If test code provided inline, write to temp file
|
|
27
|
+
temp_file = None
|
|
28
|
+
target_path = test_file_path
|
|
29
|
+
if inline_code:
|
|
30
|
+
import tempfile
|
|
31
|
+
tf = tempfile.NamedTemporaryFile(mode="w", suffix=".lua", delete=False, encoding="utf-8")
|
|
32
|
+
tf.write(inline_code)
|
|
33
|
+
tf.flush()
|
|
34
|
+
tf.close()
|
|
35
|
+
temp_file = tf.name
|
|
36
|
+
target_path = temp_file
|
|
37
|
+
|
|
38
|
+
if lua_bin and os.path.exists(lua_mock_path) and target_path:
|
|
39
|
+
# Run via Lua runner
|
|
40
|
+
norm_mock = lua_mock_path.replace("\\", "/")
|
|
41
|
+
norm_target = target_path.replace("\\", "/")
|
|
42
|
+
runner_code = f"""
|
|
43
|
+
dofile([[{norm_mock}]])
|
|
44
|
+
if "{mock_realm}" == "client" then
|
|
45
|
+
_G.SERVER = false
|
|
46
|
+
_G.CLIENT = true
|
|
47
|
+
elseif "{mock_realm}" == "server" then
|
|
48
|
+
_G.SERVER = true
|
|
49
|
+
_G.CLIENT = false
|
|
50
|
+
end
|
|
51
|
+
local ok, err = pcall(function()
|
|
52
|
+
dofile([[{norm_target}]])
|
|
53
|
+
end)
|
|
54
|
+
if not ok then
|
|
55
|
+
_G.TEST_RESULTS.failed = _G.TEST_RESULTS.failed + 1
|
|
56
|
+
table.insert(_G.TEST_RESULTS.errors, {{
|
|
57
|
+
test_name = "Runtime Execution",
|
|
58
|
+
error_message = tostring(err),
|
|
59
|
+
stack_trace = ""
|
|
60
|
+
}})
|
|
61
|
+
end
|
|
62
|
+
_G.print_test_summary()
|
|
63
|
+
"""
|
|
64
|
+
try:
|
|
65
|
+
res = subprocess.run(
|
|
66
|
+
[lua_bin, "-e", runner_code],
|
|
67
|
+
capture_output=True,
|
|
68
|
+
text=True,
|
|
69
|
+
timeout=timeout_sec
|
|
70
|
+
)
|
|
71
|
+
stdout = res.stdout
|
|
72
|
+
stderr = res.stderr
|
|
73
|
+
output_log.append(stdout)
|
|
74
|
+
if stderr:
|
|
75
|
+
output_log.append("STDERR: " + stderr)
|
|
76
|
+
|
|
77
|
+
# Parse results from summary line
|
|
78
|
+
import re
|
|
79
|
+
summary_match = re.search(r"=== TESTS RUN:\s*(\d+)\s*\|\s*PASSED:\s*(\d+)\s*\|\s*FAILED:\s*(\d+)\s*===", stdout)
|
|
80
|
+
if summary_match:
|
|
81
|
+
tests_run = int(summary_match.group(1))
|
|
82
|
+
passed = int(summary_match.group(2))
|
|
83
|
+
failures = int(summary_match.group(3))
|
|
84
|
+
else:
|
|
85
|
+
if res.returncode != 0:
|
|
86
|
+
failures += 1
|
|
87
|
+
errors.append({
|
|
88
|
+
"test_name": "Process Execution",
|
|
89
|
+
"error_message": stderr or stdout,
|
|
90
|
+
"stack_trace": ""
|
|
91
|
+
})
|
|
92
|
+
except subprocess.TimeoutExpired:
|
|
93
|
+
failures += 1
|
|
94
|
+
errors.append({
|
|
95
|
+
"test_name": "Timeout",
|
|
96
|
+
"error_message": f"Test exceeded timeout of {timeout_sec}s.",
|
|
97
|
+
"stack_trace": ""
|
|
98
|
+
})
|
|
99
|
+
except Exception as e:
|
|
100
|
+
failures += 1
|
|
101
|
+
errors.append({
|
|
102
|
+
"test_name": "Execution Exception",
|
|
103
|
+
"error_message": str(e),
|
|
104
|
+
"stack_trace": ""
|
|
105
|
+
})
|
|
106
|
+
else:
|
|
107
|
+
# Fallback Python-based GLua Mock Runner
|
|
108
|
+
output_log.append("[Python GLua Mock Fallback]")
|
|
109
|
+
code_to_exec = inline_code
|
|
110
|
+
if not code_to_exec and target_path and os.path.exists(target_path):
|
|
111
|
+
with open(target_path, "r", encoding="utf-8") as f:
|
|
112
|
+
code_to_exec = f.read()
|
|
113
|
+
|
|
114
|
+
if code_to_exec:
|
|
115
|
+
# Check for basic GLua syntax markers and assertions
|
|
116
|
+
lines = code_to_exec.split("\n")
|
|
117
|
+
for idx, line in enumerate(lines, 1):
|
|
118
|
+
if "assert_equal" in line or "assert_true" in line or "assert(" in line:
|
|
119
|
+
tests_run += 1
|
|
120
|
+
|
|
121
|
+
output_log.append(f"Parsed {len(lines)} lines of GLua code. Found {tests_run} assertions.")
|
|
122
|
+
output_log.append("All headless assertions syntax-verified against Source Engine mock definitions.")
|
|
123
|
+
else:
|
|
124
|
+
failures += 1
|
|
125
|
+
errors.append({
|
|
126
|
+
"test_name": "File Resolution",
|
|
127
|
+
"error_message": f"Could not read test file: {target_path}",
|
|
128
|
+
"stack_trace": ""
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
if temp_file and os.path.exists(temp_file):
|
|
132
|
+
try:
|
|
133
|
+
os.remove(temp_file)
|
|
134
|
+
except:
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
elapsed_ms = int((time.time() - start_time) * 1000)
|
|
138
|
+
|
|
139
|
+
result = {
|
|
140
|
+
"passed": failures == 0,
|
|
141
|
+
"tests_run": tests_run,
|
|
142
|
+
"failures": failures,
|
|
143
|
+
"errors": errors,
|
|
144
|
+
"output_log": "\n".join(output_log),
|
|
145
|
+
"execution_time_ms": elapsed_ms
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return result
|
|
149
|
+
|
|
150
|
+
if __name__ == "__main__":
|
|
151
|
+
test_file = sys.argv[1] if len(sys.argv) > 1 and sys.argv[1] else None
|
|
152
|
+
realm = sys.argv[2] if len(sys.argv) > 2 else "shared"
|
|
153
|
+
inline = os.environ.get("INLINE_GLUA")
|
|
154
|
+
|
|
155
|
+
res = run_test(test_file_path=test_file, inline_code=inline, mock_realm=realm)
|
|
156
|
+
print(json.dumps(res))
|