Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions lua/gluatest/expectations/negative.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ local TypeID = TypeID
local IsValid = IsValid
local isstring = isstring
local string_format = string.format
local Arg = include( "arg.lua" )
local GetDiff = include( "utils/table_diff.lua" )

-- Inverse checks
Expand Down Expand Up @@ -231,10 +232,53 @@ return function( subject, ... )
end
end

--- @deprecated
function expectations.haveBeenCalled()
GLuaTest.DeprecatedNotice( "to.haveBeenCalled()", "was.called()" )
return expectations.called()
end

--- Checks if the given argument is equal to the expected argument
--- Or, if the expected argument is an argument matcher, checks if the given argument matches the matcher
--- @param givenArg any The argument that was given to the stub
--- @param expectedArg any The expected argument, which can be a matcher or a value
local function checkArg( givenArg, expectedArg )
if givenArg == expectedArg then return true end

if Arg.IsArgMatcher( expectedArg ) then
local argMatcher = expectedArg --[[@as GLuaTest_StubArg]]
return argMatcher.check( givenArg )
end

return false
end

--- Expects the subject stub to have not been called with exactly the given arguments
--- @param ... any
function expectations.calledWith( ... )
assert( subject.IsStub, ".calledWith expects a stub" )
--- @cast subject GLuaTest_Stub

local expected = { ... }

for _, call in ipairs( subject.callHistory or {} ) do
if #call == #expected then
local match = true

for k = 1, #expected do
if not checkArg( call[k], expected[k] ) then
match = false
break
end
end

if match then
local formattedArgs = table.concat( expected, ", " )
i.expected( "to not have been called with arguments: %s", formattedArgs )
end
end
end
end

return expectations
end
51 changes: 46 additions & 5 deletions lua/gluatest/expectations/positive.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ local TypeID = TypeID
local IsValid = IsValid
local isstring = isstring
local string_format = string.format
local Arg = include( "arg.lua" )
local GetDiff = include( "utils/table_diff.lua" )

-- Positive checks
Expand Down Expand Up @@ -220,9 +221,10 @@ return function( subject, ... )
end

--- Expects the subject stub to have been called, optionally with an expected number of calls
--- @param n? number
--- @param n? number Expected number of calls, if nil then expects at least one call
function expectations.called( n )
assert( subject.IsStub, ".called expects a stub" )
--- @cast subject GLuaTest_Stub

local callCount = subject.callCount

Expand All @@ -237,15 +239,54 @@ return function( subject, ... )
end
end

--- @deprecated
function expectations.haveBeenCalled( n )
GLuaTest.DeprecatedNotice( "to.haveBeenCalled( number )", "was.called( number )" )
return expectations.called( n )
end

-- Soon..
--
-- function expectations.haveBeenCalledWith( ... )
-- end
-- Called Expectations

--- Checks if the given argument is equal to the expected argument
--- Or, if the expected argument is an argument matcher, checks if the given argument matches the matcher
--- @param givenArg any The argument that was given to the stub
--- @param expectedArg any The expected argument, which can be a matcher or a value
local function checkArg( givenArg, expectedArg )
Copy link

Copilot AI Jun 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The checkArg function is implemented in both positive.lua and negative.lua. Consider refactoring it into a shared helper to reduce code duplication and improve maintainability.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is all you have to say?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Getting roasted by AI is wild. 💀

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The checkArg function is implemented in both positive.lua and negative.lua. Consider refactoring it into a shared helper to reduce code duplication and improve maintainability.

I think this code duplication is fine. The function is unlikely to change or to duplicate further.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it's nbd, I think that it's probably worth moving into its own util, as expectations already have a utils dir - can't hurt

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds solid to me. 👍

if givenArg == expectedArg then return true end

if Arg.IsArgMatcher( expectedArg ) then
local argMatcher = expectedArg --[[@as GLuaTest_StubArg]]
return argMatcher.check( givenArg )
end

return false
end

--- Expects the subject stub to have been called with exactly the given arguments
--- @param ... any
function expectations.calledWith( ... )
assert( subject.IsStub, ".calledWith expects a stub" )
--- @cast subject GLuaTest_Stub

local expected = { ... }

for _, call in ipairs( subject.callHistory or {} ) do
if #call == #expected then
local match = true

for k = 1, #expected do
if not checkArg( call[k], expected[k] ) then
match = false
break
end
end

if match then return end
end
end

i.expected( "to have been called with arguments: %s", table.concat( expected, ", " ) )
end

return expectations
end
2 changes: 1 addition & 1 deletion lua/gluatest/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ GLuaTest = {
AddCSLuaFile( "gluatest/utils/version.lua" )

-- /stubs/*
AddCSLuaFile( "gluatest/stubs/stub_maker.lua" )
AddCSLuaFile( "gluatest/stubs/stubMaker.lua" )

-- /runner/*
AddCSLuaFile( "gluatest/runner/colors.lua" )
Expand Down
2 changes: 1 addition & 1 deletion lua/gluatest/runner/helpers.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
}

--- @type GLuaTest_Expect
local expect = include( "gluatest/expectations/expect.lua" )

Check warning on line 8 in lua/gluatest/runner/helpers.lua

View workflow job for this annotation

GitHub Actions / luals / check

assign-type-mismatch

Cannot assign `function` to `GLuaTest_Expect`. - `function` cannot match `GLuaTest_Expect` - Type `function` cannot match `GLuaTest_Expect`

--- @type GLuaTest_StubMaker
local stubMaker = include( "gluatest/stubs/stub_maker.lua" )
local stubMaker = include( "gluatest/stubs/stubMaker.lua" )
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you want to keep it file names lowercase for linux/workshop.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be okay, linux only has issues with dirs that start with capital letters 👍


--- Gets a unique case ID
--- @return string
Expand Down
197 changes: 197 additions & 0 deletions lua/gluatest/stubs/arg.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
local argMeta = {
__tostring = function( self )
return "Arg Matcher: " .. self.name
end,
}

--- Creates a new argument matcher
--- @param name string The name of the matcher (for use in error messages)
--- @param check fun(any): boolean A function that returns true if the argument matches
local function New( name, check )
--- @class GLuaTest_StubArg
local arg = {
--- @type string
--- The name of the matcher, used in error messages
name = name,

--- A function that returns true if the argument matches
check = check,
}

return setmetatable( arg, argMeta )
end

--- @alias GLuaTest_StubArg_Matcher fun(any): boolean

--- @class GLuaTest_StubArgs
local Arg = {
--- The metatable for all argument matchers
argMeta = argMeta,

New = New,

--- Checks if the given argument is an argument matcher
--- @param arg any The argument to check
IsArgMatcher = function( arg )
return getmetatable( arg ) == argMeta
end,

--- Matches any argument
Any = New( "Any", function( _ )
return true
end ),

--- Matches nil
Nil = New( "Nil", function( arg )
return arg == nil
end ),

--- Matches any boolean
Boolean = New( "Any Boolean", function( arg )
return arg == true or arg == false
end ),

--- Matches any number
Number = New( "Any Number", function( arg )
return type( arg ) == "number"
end ),

--- Matches any string
String = New( "Any String", function( arg )
return type( arg ) == "string"
end ),

--- Matches any table
Table = New( "Any Table", function( arg )
return type( arg ) == "table"
end ),

--- Matches any function
Function = New( "Any Function", function( arg )
return type( arg ) == "function"
end ),

--- Matches any userdata
Userdata = New( "Any Userdata", function( arg )
return type( arg ) == "userdata"
end ),

--- Matches any thread
Thread = New( "Any Thread", function( arg )
return type( arg ) == "thread"
end ),

-- GMod Types

--- Matches any Vector
Vector = New( "Any Vector", function( arg )
return type( arg ) == "Vector"
end ),

--- Matches any Angle
Angle = New( "Any Angle", function( arg )
return type( arg ) == "Angle"
end ),

--- Matches any Color
Color = New( "Any Color", function( arg )
return debug.getmetatable( arg ) == FindMetaTable( "Color" )
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

end ),

--- Matches any VMatrix
Matrix = New( "Any Matrix", function( arg )
return type( arg ) == "VMatrix"
end ),

--- Matches any Entity
Entity = New( "Any Entity", function( arg )
return type( arg ) == "Entity"
end ),

--- Matches any Player
Player = New( "Any Player", function( arg )
return type( arg ) == "Player"
end ),

--- Matches any Weapon
Weapon = New( "Any Weapon", function( arg )
return type( arg ) == "Weapon"
end ),

--- Matches any Vehicle
Vehicle = New( "Any Vehicle", function( arg )
return type( arg ) == "Vehicle"
end ),

--- Matches any Material
Material = New( "Any Material", function( arg )
return type( arg ) == "IMaterial"
end ),

--- Matches any PhysObj
PhysObj = New( "Any PhysObj", function( arg )
return type( arg ) == "PhysObj"
end ),

--- Matches any CTakeDamageInfo
DamageInfo = New( "Any CTakeDamageInfo", function( arg )
return type( arg ) == "CTakeDamageInfo"
end ),

--- Matches any CMoveData
MoveData = New( "Any CMoveData", function( arg )
return type( arg ) == "CMoveData"
end ),

--- Matches any CUserCmd
UserCmd = New( "Any CUserCmd", function( arg )
return type( arg ) == "CUserCmd"
end ),

--- Matches any CSoundPatch
SoundPatch = New( "Any CSoundPatch", function( arg )
return type( arg ) == "CSoundPatch"
end ),

--- Matches any File
File = New( "Any File", function( arg )
-- Apparently if the file doesn't exist, type( arg) will return "no value" ?
return type( arg ) == "File"
end ),

--- Matches any SurfaceInfo
SurfaceInfo = New( "Any SurfaceInfo", function( arg )
return type( arg ) == "SurfaceInfo"
end ),

--- Matches any ConVar
ConVar = New( "Any ConVar", function( arg )
return type( arg ) == "ConVar"
end ),


-- Clientside


--- Matches any Panel
Panel = New( "Any Panel", function( arg )
return type( arg ) == "Panel"
end ),

--- Matches any CLuaParticle
Particle = New( "Any Particle", function( arg )
return type( arg ) == "CLuaParticle"
end ),

--- Matches any CLuaEmitter
ParticleEmitter = New( "Any ParticleEmitter", function( arg )
return type( arg ) == "CLuaEmitter"
end ),

--- Matches any IMesh
Mesh = New( "Any Mesh", function( arg )
return type( arg ) == "IMesh"
end ),
}

return Arg
File renamed without changes.
Loading