We here at Valkyrie Transport use linkset data to store paths and other goodies. Today I’m going to start building a local version to use in developing our new software. The new functions will be folded into our
llextensions.
There are something like 14 or 15 linkset data functions. It appears that none of them are implemented in the local slua that we use to develop code. We (well, I) do that because my tools are better at home, the lua is faster, I don’t have to be in world, and, well, I like to work this way. So I need to add at least the linkset data functions that we need to the ll extensions. And I thought this would be a good topic to just blorg1 about.
I thought it might be an interesting one, as I’m starting from essentially nothing, to build up a number of new functions. Here’s what I have so far:
-- test_data_store.lua
local __ = require('../inc/expectations')
local ll = require('../inc/ll_extensions')
__:describe('Link Set Data', function()
__:test('reset available', function()
ll.LinksetDataReset()
__:expect(ll.LinksetDataAvailable()).is(131072)
end)
__:test('write and read', function()
end)
end)
__:execute()
The test just requires expectations, the testing framework, and ll_extensions, the require that replaces the provided ll variable with our extended version, where we have added in the additional functions that Linden doesn’t provide.
I’ve enhanced the basic ll_extensions to expect to require in a new file for the linkset data stuff:
-- ll_extensions.lua
local _LSD = require('./ll_linkset_data')
-- ...
local _extensions = {
Rot2Euler = _rot_to_euler,
Euler2Rot = _euler_to_rot,
}
for k, v in _LSD do
_extensions[k] = v
end
if ll.Euler2Rot ~=nil then return ll end
setmetatable(_extensions, {__index=ll})
return _extensions
I’ll move the Rot extensions to their own file shortly, but left it included in the extensions file for now.
Finally, here is the linkset code so far:
local _LSD = {}
local _LSData = {}
function _LSD:LinksetDataAvailable()
return 131072
end
function _LSD:LinksetDataReset()
_LSData = {}
end
return _LSD
That’s kind of in the “fake it till you make it” mode that Kent Beck wrote about when he was first describing test-driven development. I don’t claim to be as good as he is, nor even to be doing just what he described, but I do certainly try to work in that style, as we’ll see going forward from here.
The tests run OK at this point. My plan now that you’re up to date is to do a simple write and read test, and then we’ll see. You can see the empty ‘write and read’ test ready for me to put something into it.
__:test('write and read', function()
ll.LinksetDataWrite('some key', 'its value')
local found = ll.LinksetDataRead('some key')
__:expect(found).is('its value')
end)
Simple enough. Fails, of course:
Feature: Link Set Data
Error: /Users/ron/Desktop/vscode/bez_dev/tst/test_data_store.lua:15: attempt to call a nil value. Test: 'write and read'.
Link Set Data, 1 Expects: Pass 1, Fail 1. Ignored 0.
Now a true fake it approach might provide empty functions and see a different fail, but I’m feeling strong and I’m gonna go for more capability.
function _LSD:LinksetDataRead(key)
return _LSData[key] or ''
end
function _LSD:LinksetDataWrite(key, value)
_LSData[key] = value
return LINKSETDATA_OK
end
Note that not all the details of how those functions work are dealt with. Truth be told, we might never deal with some of them: I really only plan to build what we’ll need, and we don’t use the password stuff and I might not even deal with running out of storage. (On second thought, the storage limits might be useful, so we must might do that at some point, maybe even a rough cut today.)
I think my test should run now, let’s find out why I’m wrong.
Feature: Link Set Data
Actual: , Expected: its value. Test: 'write and read'.
Link Set Data, 2 Expects: Pass 1, Fail 1. Ignored 0.
Hm, we seem to have returned an empty string. That’s … unexpected. But I often make mistakes, this is just the most recent.
Ah. The bug is that I’m defining these functions with :, not with .. They will be called with ll., as usual. Didn’t show up until I had parameters.
function _LSD.LinksetDataRead(key)
return _LSData[key] or ''
end
function _LSD.LinksetDataWrite(key, value)
_LSData[key] = value
return LINKSETDATA_OK
end
Tests run. Ship it!
Let’s think about what happened here. I am used to defining classes to do my work in Lua and other languages. But there is no point to making the LSD stuff into a class. More accurately, I don’t see enough value to doing so, so it is just a table of functions. There can be only one LSD seen by your script, so we don’t really need instances of it, so I decided to just make it as a table of functions, but my typing reflexes put colon where I needed dot.
We may subsequently decide that we need more than one LSD at a time, for example if we were trying to develop code where two or more objects communicate. But that’s a long way from what I know how to do now. I know how to build simple scripts that use objects to carry out functions in a single-object environment. If we need to go beyond that, we’ll figure it out then.
OK, let’s get back to it. I think I’d like to have another test for read and write, and then maybe we should do something about available space.
__:test('multiple write and read', function()
ll.LinksetDataWrite('key1', 'value 1')
ll.LinksetDataWrite('key2', 'value 2')
__:expect(ll.LinksetDataRead('key1')).is('value 1')
__:expect(ll.LinksetDataRead('key2')).is('value 2')
end)
Of course I expect this to work. It does:
Feature: Link Set Data
Link Set Data, 4 Expects: Pass 4, Fail 0. Ignored 0.
Let’s see what we can do about available while we’re here. The documents indicate that the storage consumed is just the sum of the sizes of the keys and values, plus 32 bytes if you use the password notion. So we can predict available and then implement it.
__:test('multiple write and read', function()
ll.LinksetDataWrite('key1', 'value 1')
ll.LinksetDataWrite('key2', 'value 2')
__:expect(ll.LinksetDataRead('key1')).is('value 1')
__:expect(ll.LinksetDataRead('key2')).is('value 2')
local used = #'key1' + #'key2' + #'value 1' + #'value 2'
__:expect(used).is(22)
__:expect(ll.LinksetDataAvailable()).is(131072-used)
end)
I just added a couple of checks to the existing test. TDD purists would burn me at the stake for testing “so much” in one test. My blessings to them. Let’s make this test pass:
local function used()
local tot = 0
for k, v in _LSData do
tot += (#k + #v)
end
return tot
end
function _LSD.LinksetDataAvailable()
return 131072 - used()
end
There was an option here to keep track of the available space as we go, adding in as we write, subtracting as we delete, and so on. That seems likely to lead to errors. We’ll go with this unless it gives us trouble somehow.
Let’s do one more function, the delete.
__:test('delete', function()
ll.LinksetDataWrite('key_a', 'value_a')
ll.LinksetDataWrite('key_b', 'value_b')
ll.LinksetDataWrite('key_c', 'value_c')
__:expect(ll.LinksetDataRead('key_b')).is('value_b')
local result_ok = ll.LinksetDataDelete('key_b')
__:expect(result_ok).is(LINKSETDATA_OK)
local result_nf = ll.LinksetDataDelete('key_b')
__:expect(result_nf).is(LINKSETDATA_NOTFOUND)
__:expect(ll.LinksetDataRead('key_a')).is('value_a')
__:expect(ll.LinksetDataRead('key_c')).is('value_c')
__:expect(ll.LinksetDataRead('key_b')).is('')
end)
Wow, intricate test. I know people who would frown at me for that. I don’t have a better idea, so we’ll go with this.
function _LSD.LinksetDataDelete(key)
local v = _LSData[key]
if not v then return LINKSETDATA_NOTFOUND end
_LSData[key] = nil
return LINKSETDATA_OK
end
We check to see if we have it to produce the not found return, and if we do find it then we delete it (nil) and return the OK return. Tests pass:
Feature: Link Set Data
Link Set Data, 12 Expects: Pass 12, Fail 0. Ignored 0.
I think that’ll do for this little recipe / tutorial / blorg. It’s also pretty close to all we need for our future work with linkset data. Let’s do a quick review.
I should have mentioned that I added a setup to the tests, to reset the linkset data table before each test, so that they didn’t interfere with each other:
__:describe('Link Set Data', function()
__:setup(function()
ll.LinksetDataReset()
end)
The process I use, if you can call it a process, is that I think of the things we need, here things like read and write and delete and available, and I think of simple tests with simple code to implement them. It was particularly easy here, since the whole capability is little more than a very simple dictionary.
The implementation is just a table of functions … since what ll is is just a table of functions. We presently have about 50 lines of tests and 50 lines of production code. Perhaps with your LLM you’ve created 500 lines this morning, or 5000. Maybe you had as much fun as I did and got the same pleasure of a job well done. I hope you did.
Safe paths!
Something between “blog” and “arrg”, I guess, maybe with a nod to “blurt”. Basically my term for the stream-of-consciousness articles like this one. ↩