Variables declared in test setup must be nilable? - sorbet

I'm writing a test where I have a variable that I need to declare in the setup do.
typed: strict
frozen_string_literal: true
class BunnyTest
extend(T::Sig)
setup do
#bunny = T.let(Bunny.new, Bunny)
end
test "#hop works correctly" do
assert(#bunny.hop)
end
end
When I run the type check, I get a message saying:
The instance variable #bunny must be declared inside initialize or declared nilable
Is there a way to treat ivar declarations in setup do just like ivar declarations in #initialize?

There are a couple of alternatives for this
A rewriter
If you use Minitest, there's a rewriter in Sorbet that will make this work for you.
You could write one for your testing framework of choice
__initialize + method
This gets verbose, specially if you have more than one value you want to do this for
class BunnyTest
extend(T::Sig)
def __initialize__
#bunny = T.let(nil, Bunny)
end
setup do
#bunny = Bunny.new
end
def bunny
T.must(#bunny)
end
test "#hop works correctly" do
assert(bunny.hop)
end
end
Avoid setup
This is not responding your question, but an alternative that makes it unnecessary. I know there are cases where setup is helpful, but as a rule of thumb, I avoid it, and use explicit initialization in my test case. This makes it way easier to follow what's going on by just looking at the case, and avoids side effects/changes needed for other cases affecting the others:
class BunnyTest
extend(T::Sig)
sig {returns(Bunny)}
def bunny
Bunny.new
end
test "#hop works correctly" do
assert(bunny.hop)
end
test "can #hop twice" do
my_bunny = bunny
assert(bunny.hop)
assert(bunny.hop, "Should be able to hop a second time")
end
end

Related

Inherit from table returned from function

There is an API provided function, let's call it createBase which returns a table (object). I want to add methods to this table, but I can't just do x = createBase() and then function x:foo() because I have another function similar to createBase, but it's createExtended. It might be easier to explain with the code I have so far:
import api --I don't know how you'd do this in vanilla Lua, I'd use os.loadAPI("api") but that's computercraft specific, I think
Extended = {}
function Extended:foo()
print("foo from extended")
end
function createExtended(params)
x = api.createBase(params)
Extended.__index = x
return Extended --this is obviously wrong: you can't return a class and expect it to be an object
end
Of course, this doesn't work: but I don't know how I might make it work either. Let's assume the table returned by createBase has a function called bar which just prints bar from base. With this test code, the following outputs are given:
e = createExtended()
e.foo() --prints "foo from extended"
e.bar() --nil, therefor error
How can I make this possible, short of defining function x.bar() inside createExtended?
Thanks in advance.
The very simplest way is to attach the method to it directly, instead of using a metatable.
local function extend(super_instance)
super_instance.newMethod = newMethod
return super_instance
end
local function createExtended(...)
return extend(createSuper(...))
end
This will work, unless your superclass uses __newindex (for example, preventing you from writing to unknown properties/methods), or iterates over the keys using pairs or next, since it will now have an additional key.
If for some reason you cannot modify the object, you will instead have to 'wrap' it up.
You could make a new instance which "proxies" all of its methods, properties, and operators to another instance, except that it adds additional fields and methods.
local function extend(super_instance)
local extended_instance = {newMethod = newMethod}
-- and also `__add`, `__mul`, etc as needed
return setmetatable(extended_instance, {__index = super_instance, __newindex = super_instance})
end
local function createExtended(...)
return extend(createSuper(...))
end
This will work for simple classes, but won't work for all uses:
Table iteration like pairs and next won't find the keys from the original table, since they're not actually there. If the superclass inspects the metatable of the object it is given (or if the superclass is actually a userdata), it will also not work, since you'll find the extension metatable instead.
However, many pure-Lua classes will not do those things, so this is still a fairly simple approach that will probably work for you.
You could also do something similar to Go; instead of having a way to 'extend' a class, you simply embed that class as a field and offer convenience to directly calling methods on the wrapping class that just call the methods on the 'extended' class.
This is slightly complicated by how 'methods' work in Lua. You can't tell if a property is a function-that-is-a-property or if it's actually a method. The code below assumes that all of the properties with type(v) == "function" are actually methods, which will usually be true, but may not actually be for your specific case.
In the worst case, you could just manually maintain the list of methods/properties you want to 'proxy', but depending on how many classes you need to proxy and how many properties they have, that could become unwieldy.
local function extend(super_instance)
return setmetatable({
newMethod = newMethod, -- also could be provided via a more complicated __index
}, {
__index = function(self, k)
-- Proxy everything but `newMethod` to `super_instance`.
local super_field = super_instance[k]
if type(super_field) == "function" then
-- Assume the access is for getting a method, since it's a function.
return function(self2, ...)
assert(self == self2) -- assume it's being called like a method
return super_field(super_instance, ...)
end
end
return super_field
end,
-- similar __newindex and __add, etc. if necessary
})
end
local function createExtended(...)
return extend(createSuper(...))
end

Overridable Macro for exunit test cases

I am writing test cases for my application and most of my controllers contain common code for CRUDs so I have written common macro and use it inside my controllers. Test cases for all of the controllers would be written automatically. But I am confused about how to make this common code overridable so that I can override whenever I want.
defmodule Qserv.ControllerTest do
defmacro __using__(_options) do
quote location: :keep do
use Qserv.Web.ConnCase, async: true
# this kernel will give me access to current `#model` and `#controller`
use Qserv.KernelTest
describe "#{#controller}.create/2" do
test "All required fields set `required` in model should generate errors that these fields are missing -> One, two, All"
test "Only required fields should create record and match the object"
end
# defoverridable index: 2, I want to override above `describe` completely or/and the included test cases
end
end
end
Any help/idea how to achieve this?
I am generally not a fan of the "let's do things to undo it later". It generally forces developers to keep a stack in their head of how things are added and removed later on.
In this case in particular, you are coupling on the test name. Imagine someone decides to make the "two" in "One, two, All" uppercase. Now all of the future overrides won't apply and you will have duplicate tests.
A better solution to explicit opt in what you need. For example, you can define smaller macros that you use when necessary:
describe_create!
describe_update!
...
describe_delete!
Maybe you could have describe_restful! that invokes all of them. The lesson here is to have small building blocks that you build on top of instead of having a huge chunk that you try to break apart later.
PS: please use better names than the describe_x that I used. :)

Virtual attributes with multiple parameters

I have the following function that works just fine
def holiday_hours_for(holiday)
hours["holiday_hours"][holiday.to_s] if hours["holiday_hours"][holiday.to_s]
end
I am just learning about virtual attributes, and am having some trouble figuring out the setter version of this function. How would I achieve this function...
def holiday_hours_for(holiday)=(hours)
self.hours["holiday_hours"][holiday.to_s] = hours if hours["holiday_hours"][holiday.to_s]
end
Thanks!
UPDATE: I came up with the following, is this the best way?
def update_holiday_hours_for(holiday, hours_text)
self.hours = Hash.new unless hours
self.hours["holiday_hours"] = Hash.new unless hours["holiday_hours"]
self.hours["holiday_hours"][holiday.to_s] = hours_text.to_s
end
the important thing to understand is that setter methods are defined with an "=" sign at the end. like this:
def holiday_hours=(some_parameters)
# some code
end
this behaves like a setter method for the instance variable #holiday_hours. The method name is "holiday_hours=" and it takes one or more parameters, as required in your app to derive the value for the attribute #holiday_hours. When Ruby sees some code like
holiday.holiday_hours = some_value
it invokes the setter method you've defined. Even though there is some whitespace in this assignment that aren't in the setter method. Ruby interprets this assignment as
holiday.holiday_hours=(some_value)
calling the holiday_hours= method on the holiday object, with argument some_value
It's not clear from your post what the class is in which your example methods dwell, I can guess what the variable hours_text is, but what is the parameter holiday?

MATLAB - why is it complaining about #-folders for classdef'd objects?

I've been writing OOP MATLAB code for quite some time. However, I'm now running MATLAB code on a Windows machine for the first time.
I have the following code:
classdef myClass < handle
properties
i
end
methods
function obj = myClass()
obj.i = 0;
end
function say(obj)
obj.i = obj.i + 1;
fprintf('This is time #%i you invoked me!\n', obj.i);
end
end
end
Seems pretty innocuous. I try to instantiate an object and I get this:
>> m = myClass;
Error using myClass
Error: File: myClass.m Line: 1 Column: 10
A class definition must be an "#" directory.
I've never used an #-folder in all my time writing OOP MATLAB code. My understand is it's required if class methods are written separately from the classdef file (mine's not) or if it's using the old-style MATLAB class syntax (mine's not).
I think I know what the deal is and I wanted to see if there's a workaround: My working directory is of the form
C:\Users\DangKhoa#MyCompany.com\Documents\MATLAB
Is that # throwing MATLAB off and making the computer think I'm in an #-folder? If it is, is there a workaround (aside making a new user on my computer, obviously - and that probably isn't doable)? If not, what is going on?
Looks like yes, the # in the middle of the folder is causing the error. I filed a bug report with The MathWorks.

How to access the object itself in With ... End With

Some code to illustrate my question:
With Test.AnObject
.Something = 1337
.AnotherThing = "Hello"
''// why can't I do this to pass the object itself:
Test2.Subroutine(.)
''// ... and is there an equivalent, other than repeating the object in With?
End With
There is no way to refer to the object referenced in the With statement, other than repeating the name of the object itself.
EDIT
If you really want to, you could modify your an object to return a reference to itself
Public Function Self() as TypeOfAnObject
Return Me
End Get
Then you could use the following code
With Test.AnObject
Test2.Subroutine(.Self())
End With
Finally, if you cannot modify the code for an object, you could (but not necessarily should) accomplish the same thing via an extension method. One generic solution is:
' Define in a Module
<Extension()>
Public Function Self(Of T)(target As T) As T
Return target
End Function
called like so:
Test2.Subroutine(.Self())
or
With 1
a = .Self() + 2 ' a now equals 3
End With
I suspect you'll have to repeat yourself. If the expression (to get the object) is expensive, then perhaps drop it into a variable first, and either use that variable in the With, or drop the With completely:
tmp = Test.AnObject;
tmp.Something = 1337;
...
Test2.Subroutine(tmp);
As others have said, you're going to have to write
Test2.Subroutine(Test.AnObject)
This is a good example of why it's worth being a little careful with the With construct in VB.Net. My view is that to make it worth using at all, you really need to be setting more than one or two properties, and/or calling more than one or two methods on the object in the With statement.
When there are lots, and you're not interspersing the .SomeProperty = , or .DoSomething, with other things, it's a terrific aid to readability.
Conversely, a few dots sprinkled amongst a load of other stuff is actually a lot harder to read than not using With at all.
In this case, . characters on their own could easily get lost visually, although of course, it would be syntactically consistent.
I guess they just chose not to implement it. VB isn't really the sort of language where they want to encourage single character language elements, and as a heavy user of VB.Net, I broadly agree with that.
Bottom line: if you're using a With clause with many contained elements, having to refer to the object itself isn't that big a deal. If you're using it with just one or two, maybe better not to use a With clause in the first place.
I'm not sure this is an "answer", per se, but it does illustrate another reason to want a short-hand reference to the parent in a With.
Here's a code sample using a "bare With" (that's what I call it, anyway):
With New frmMySubForm
.lblLinkLabel.Links.Add(New LinkLabel.Link With {.Name = "link", .LinkData = "someUrl", .Start = .lblLinkLabel.Text.IndexOf("link"), .Length = "link".Length})
...
End With
But you actually can't code that because in the term .Start = .lblLinkLabel.Text.IndexOf("link") the compiler expects anything starting with . to be a member of LinkLabel.Link, which .lblLinkLabel isn't.
What would be good, I think, is to be able to write something like:
With New frmMySubForm
.lblLinkLabel.Links.Add(New LinkLabel.Link With {.Name = "link", .LinkData = "someUrl", .Start = Me.lblLinkLabel.Text.IndexOf("link"), .Length = "link".Length})
...
End With
where Me in this scope is taken to be New frmMySubForm.
Yes, I realize that I'm being picky and I could easily assign a variable, etc. But the example form is something I use a lot simply out of preference.