Commit Diff


commit - d1d1722e0b50fe00258caf09f715a2703160fbdc
commit + 897a4d92041aabef45cfe2e5d1de692d931d8ed0
blob - 39595320a8ac12823d56ec1c3977b98aa4fb2acb
blob + 74708d3ef4936fc54882ae5b3a8ad12332da9429
--- CHANGELOG.md
+++ CHANGELOG.md
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](http
 
 ### Added
 
+- A CAS-register generator.
+
 ### Changed
 
 - Bump luacheck version.
blob - 9a835425d5023728e3a352f7e9ad0fdf7b5d438d
blob + 80ebdc35abdf22cf218ddbf61d1f567213f9002f
--- molly/tests.lua
+++ molly/tests.lua
@@ -69,6 +69,26 @@
 -- except nil. Null values in Lua tables are represented as JSON null
 -- (`json.NULL`, a Lua `lightuserdata` NULL pointer) is provided for
 -- comparison.
+--
+--### CAS-Register
+--
+-- Generator produces concurrent atomic updates to a shared
+-- register. Writes are assumed to be unique, but this is the only
+-- constraint.
+--
+-- Operations are of three forms:
+--
+--     { "r", "x", 1 } denotes a read of `x` observing the value 1.
+--     { "w", "x", 2 } denotes a write of `x`, settings its value to 2.
+--     { "cas", "x", 2 } denotes a CAS of `x`, settings its value to 2.
+--
+-- Example of history:
+--
+--     { type = "invoke", f = "cas", value = { 1, 5 },  process = 0, index = 1}
+--     { type = "ok",     f = "fail", value = { 1, 5 }, process = 0, index = 2}
+--     { type = "invoke", f = "write", value = { 2 },   process = 0, index = 3}
+--     { type = "ok",     f = "write", value = { 2 },   process = 0, index = 4}
+--
 
 local math = require('math')
 
@@ -135,8 +155,81 @@ end
 -- @function rw_register_gen
 local function rw_register_gen()
     return gen_lib.cycle(gen_lib.iter({ op_r, op_w }))
+end
+
+-- Function that describes a 'read' operation.
+local function cas_op_r()
+    return setmetatable({
+        f = 'read',
+        value = {
+            json.NULL,
+        },
+    }, {
+        __type = '<operation>',
+        __tostring = function(self)
+            return '<read>'
+        end,
+    })
+end
+
+-- Function that describes a 'write' operation.
+local function cas_op_w()
+    return setmetatable({
+        f = 'write',
+        value = {
+            math.random(1, 100),
+        }
+    }, {
+        __type = '<operation>',
+        __tostring = function(self)
+            return '<write>'
+        end,
+    })
 end
 
+-- Function that describes a 'cas' operation.
+local function cas_op_cas()
+    return setmetatable({
+        f = 'cas',
+        value = {
+            math.random(1, 100),
+            math.random(1, 100),
+        }
+    }, {
+        __type = '<operation>',
+        __tostring = function(self)
+            return '<cas>'
+        end,
+    })
+end
+
+--- CAS (Compare-And-Set) operations generator.
+--
+-- @usage
+--
+-- > log = require('log')
+-- > tests = require('molly.tests')
+-- > for _it, v in tests.cas_register_gen() do log.info(v()) end
+-- {"f":"read","value":[null]}
+-- {"f":"write","value":[80]}
+-- {"f":"cas","value":[70,60]}
+-- {"f":"read","value":[null]}
+-- {"f":"write","value":[76]}
+-- {"f":"cas","value":[9,67]}
+-- {"f":"read","value":[null]}
+-- {"f":"write","value":[34]}
+-- {"f":"cas","value":[74,43]}
+-- {"f":"read","value":[null]}
+-- ---
+-- ...
+--
+-- @return an iterator
+--
+-- @function cas_register_gen
+local function cas_register_gen()
+    return gen_lib.cycle(gen_lib.iter({ cas_op_r, cas_op_w, cas_op_cas }))
+end
+
 -- Function that describes a 'list' micro operation.
 local function mop_list(key_count)
     return {
@@ -242,4 +335,5 @@ end
 return {
     list_append_gen = list_append_gen,
     rw_register_gen = rw_register_gen,
+    cas_register_gen = cas_register_gen,
 }
blob - 5c6f8131b0a73944141e4af7301b5dcb258f2599
blob + 176082a51d2c02c36c3f6ff9a2b36654b8135d18
--- test/tests.lua
+++ test/tests.lua
@@ -27,7 +27,7 @@ local utils = molly.utils
 local seed = os.time()
 math.randomseed(seed)
 
-test:plan(10)
+test:plan(11)
 
 test:test('clock', function(test)
     test:plan(5)
@@ -156,6 +156,33 @@ test:test('gen', function(test)
     test:ok(passed_time - timeout < eps, "gen.time_limit()")
 end)
 
+test:test('tests.cas_register_gen', function(test)
+    test:plan(4)
+
+    local num = 5
+    local n = tests.cas_register_gen():take(5):length()
+    test:is(n, num, 'tests.cas_register_gen(): length')
+
+    local gen, param, state = tests.cas_register_gen()
+    local _, res = gen(param, state)
+    res = res()
+    local f = res.f
+    local value = res.value
+    test:ok(f == 'cas' or
+	        f == 'write' or
+			f == 'read', 'tests.cas_register_gen(): function')
+    test:is(type(value), 'table', 'tests.cas_register_gen(): value type')
+    if f == 'cas' then
+        test:ok(#value, 2, 'tests.cas_register_gen(): cas value')
+    end
+    if f == 'read' then
+        test:ok(#value, 0, 'tests.cas_register_gen(): read value')
+    end
+    if f == 'write' then
+        test:ok(#value, 1, 'tests.cas_register_gen(): write value')
+    end
+end)
+
 local IDX_MOP_TYPE = 1
 local IDX_MOP_KEY = 2
 local IDX_MOP_VAL = 3