From 1e39d7da21ed904f0344a166c4597708df9463ef Mon Sep 17 00:00:00 2001 From: Javier Guerra Date: Tue, 15 Apr 2014 03:11:26 -0500 Subject: [PATCH 1/4] test framework --- src/Makefile | 2 +- src/apps/basic/basic_apps.lua | 50 +++++++++ src/apps/intel/intel10g_t.lua | 199 ++++++++++++++++++++++++++++++++++ src/apps/intel/intel_app.lua | 1 + src/apps/intel/intel_t.lua | 24 ++++ src/core/config.lua | 2 +- src/core/lib.lua | 22 ---- src/core/packet_t.lua | 143 ++++++++++++++++++++++++ src/lib/index_set_t.lua | 18 +++ src/lib/macaddress_t.lua | 16 +++ src/test.lua | 66 +++++++++++ 11 files changed, 519 insertions(+), 24 deletions(-) create mode 100644 src/apps/intel/intel10g_t.lua create mode 100644 src/apps/intel/intel_t.lua create mode 100644 src/core/packet_t.lua create mode 100644 src/lib/index_set_t.lua create mode 100644 src/lib/macaddress_t.lua create mode 100644 src/test.lua diff --git a/src/Makefile b/src/Makefile index c8d9c93288..716e347b22 100644 --- a/src/Makefile +++ b/src/Makefile @@ -7,7 +7,7 @@ E= @echo SRCDIR = $(shell find . -type d -not -regex './obj.*' -printf '%P ') OBJDIR = $(patsubst %,obj/%,$(SRCDIR)) -LUASRC = $(shell find . -regex '[^\#]*\.lua' -printf '%P ') +LUASRC = $(shell find . -regex '[^\#]*\.lua' ! -name '*_t.lua' -printf '%P ') CSRC = $(shell find . -regex '[^\#]*\.c' -printf '%P ') CHDR = $(shell find . -regex '[^\#]*\.h' -printf '%P ') ASM = $(shell find . -regex '[^\#]*\.dasc' -printf '%P ') diff --git a/src/apps/basic/basic_apps.lua b/src/apps/basic/basic_apps.lua index 961334ccfc..a5bae08ff9 100644 --- a/src/apps/basic/basic_apps.lua +++ b/src/apps/basic/basic_apps.lua @@ -1,5 +1,6 @@ module(...,package.seeall) +local lib = require("core.lib") local app = require("core.app") local buffer = require("core.buffer") local packet = require("core.packet") @@ -221,3 +222,52 @@ function Buzz:pull () print "bzzz pull" end function Buzz:push () print "bzzz push" end +--- ### Classifier app: +--- records history of packets +Classifier = setmetatable({}, {__index = Basic}) +function Classifier:new() + return setmetatable( + {_patterns={}, _rev={}, _counters={}, _seq={}}, + {__index=Classifier}) +end + +function Classifier:setPatterns(pats) + self._patterns = pats + self._seq = {} + self._rev = {} + self._counters = {} + for k,v in pairs(pats) do + self._rev[v] = k + self._counters[k] = 0 + end +end + +function Classifier:count(ln, d) + local packet_name = self._rev[d] + if packet_name == nil then + packet_name = #self._patterns+1 + self._patterns[packet_name] = d + self._rev[d] = packet_name + end + self._seq[#self._seq+1] = ln..':'..packet_name + self._counters[packet_name] = (self._counters[packet_name] or 0) + 1 +end + +function Classifier:repConters() + return table.concat(self._counters, ',') +end + +function Classifier:repSequence() + return table.concat(self._seq, ',') +end + +function Classifier:push () + for ln, l in pairs(self.input) do + for _ = 1, link.nreadable(l) do + local p = link.receive(l) + self:count(ln, packet.tostring(p)) + packet.deref(p) + end + end +end + diff --git a/src/apps/intel/intel10g_t.lua b/src/apps/intel/intel10g_t.lua new file mode 100644 index 0000000000..df3e3d102f --- /dev/null +++ b/src/apps/intel/intel10g_t.lua @@ -0,0 +1,199 @@ +local lib = require("core.lib") +local packet = require "core.packet" +local link = require("core.link") +local config = require("core.config") +local app = require("core.app") +local basic_apps = require("apps.basic.basic_apps") +local Intel82599 = require "apps.intel.intel_app".Intel82599 + + +local PCIdevA, PCIdevB = '0000:05:00.0', '0000:8a:00.0' + + +local function _unhex(s) + local d = {} + for b in s:gmatch('[0-9a-fA-F][0-9a-fA-F]') do + d[#d+1] = tonumber(b, 16) + end + return string.char(unpack(d)) +end + +local function pad(s, n) + if not n then return s end + return (s..('\0'):rep(n)):sub(1, n) +end + +local function make_packet(s, padlen) + return packet.from_data( + _unhex('FF:FF:FF:FF:FF:FF').. + _unhex('52:54:00:01:01:01').. + _unhex('08:00').. + pad(s, padlen).. + _unhex('00 00 00 00') + ) +end + +local function make_split_packet(s, padlen) + return packet.from_data( + _unhex('FF:FF:FF:FF:FF:FF').. + _unhex('52:54:00:01:01:01').. + _unhex('08:00'), + pad(s, padlen).. + _unhex('00 00 00 00') + ) +end + +local function _set_dst(p, dst) + if #dst ~= 6 then dst = _unhex(dst) end + assert(#dst==6, "wrong address length") + packet.fill_data(p, dst, 0) + return p +end + +local function _set_src(p, src) + if #src ~= 6 then src = _unhex(src) end + assert(#src==6, "wrong address length") + packet.fill_data(p, src, 6) + return p +end + + +return { + sf_to_sf = { + __setup = function () + local c = config.new() + config.app(c, 'source1', basic_apps.Join) +-- config.app(c, 'source2', basic_apps.Source) + config.app(c, 'nicA', Intel82599, ([[{pciaddr='%s'}]]):format(PCIdevA)) + config.app(c, 'nicB', Intel82599, ([[{pciaddr='%s'}]]):format(PCIdevB)) + config.app(c, 'sink', basic_apps.Classifier) + config.link(c, 'source1.out -> nicA.rx') +-- config.link(c, 'source2.out -> nicB.rx') + config.link(c, 'nicA.tx -> sink.in1') + config.link(c, 'nicB.tx -> sink.in2') + app.configure(c) + end, + + t = { + single_iovec = { + broadcast = function () + local p = make_packet('some payload', 42) + _set_dst(p, 'FF:FF:FF:FF:FF:FF') + app.app_table.sink:setPatterns({same=packet.tostring(p)}) + link.transmit(app.app_table.source1.output.out, p) + app.main({duration = 1, report={showlinks=false}}) + assert(app.app_table.sink:repSequence() == 'in2:same') + end, + unicast = function () + local p = make_packet('some payload', 42) + _set_dst(p, '52:54:00:01:01:01') + app.app_table.sink:setPatterns({same=packet.tostring(p)}) + link.transmit(app.app_table.source1.output.out, p) + app.main({duration = 1, report={showlinks=false}}) + assert(app.app_table.sink:repSequence() == 'in2:same') + end, + }, + + split_header = { + broadcast = function () + local p = make_split_packet('some payload', 42) + _set_dst(p, 'FF:FF:FF:FF:FF:FF') + app.app_table.sink:setPatterns({same=packet.tostring(p)}) + link.transmit(app.app_table.source1.output.out, p) + app.main({duration = 1, report={showlinks=false}}) + assert(app.app_table.sink:repSequence() == 'in2:same') + end, + unicast = function () + local p = make_split_packet('some payload', 42) + _set_dst(p, '52:54:00:01:01:01') + app.app_table.sink:setPatterns({same=packet.tostring(p)}) + link.transmit(app.app_table.source1.output.out, p) + app.main({duration = 1, report={showlinks=false}}) + assert(app.app_table.sink:repSequence() == 'in2:same') + end, + }, + }, + }, + + sf_to_vf = { + __setup = function () + local c = config.new() + config.app(c, 'source1', basic_apps.Join) + config.app(c, 'nicAs', Intel82599, ([[{ + -- Single App on NIC A + pciaddr = '%s', + macaddr = '52:54:00:01:01:01', + }]]):format(PCIdevA)) + config.app(c, 'nicBm0', Intel82599, ([[{ + -- first VF on NIC B + pciaddr = '%s', + vmdq = true, + macaddr = '52:54:00:02:02:02', + }]]):format(PCIdevB)) + config.app(c, 'nicBm1', Intel82599, ([[{ + -- second VF on NIC B + pciaddr = '%s', + vmdq = true, + macaddr = '52:54:00:03:03:03', + }]]):format(PCIdevB)) + config.app(c, 'sink', basic_apps.Classifier) + config.link(c, 'source1.out -> nicAs.rx') + config.link(c, 'nicAs.tx -> sink.in1') + config.link(c, 'nicBm0.tx -> sink.in2') + config.link(c, 'nicBm1.tx -> sink.in3') + app.configure(c) + end, + t = { + single_iovec = { + broadcast = function () + local p = make_packet('broadcast content', 42) + _set_dst(p, 'FF:FF:FF:FF:FF:FF') + app.app_table.sink:setPatterns({same=packet.tostring(p)}) + link.transmit(app.app_table.source1.output.out, p) + app.main({duration = 1, report={showlinks=false}}) + assert(app.app_table.sink:repSequence() == 'in2:same,in3:same') + end, + unicast = function () + local p = make_packet('some payload', 42) + _set_dst(p, '52:54:00:02:02:02') + app.app_table.sink:setPatterns({same=packet.tostring(p)}) + link.transmit(app.app_table.source1.output.out, p) + app.main({duration = 1, report={showlinks=false}}) + assert(app.app_table.sink:repSequence() == 'in2:same') + end, + }, + split_header = { + broadcast = function () + local p = make_split_packet('broadcast content', 42) + _set_dst(p, 'FF:FF:FF:FF:FF:FF') + app.app_table.sink:setPatterns({same=packet.tostring(p)}) + link.transmit(app.app_table.source1.output.out, p) + app.main({duration = 1, report={showlinks=false}}) + assert(app.app_table.sink:repSequence() == 'in2:same,in3:same') + end, + unicast = function () + local p = make_split_packet('some payload', 42) + _set_dst(p, '52:54:00:02:02:02') + app.app_table.sink:setPatterns({same=packet.tostring(p)}) + link.transmit(app.app_table.source1.output.out, p) + app.main({duration = 1, report={showlinks=false}}) + assert(app.app_table.sink:repSequence() == 'in2:same') + end, + }, + + + }, + }, + + local_A_dev = { + Broadcast = function () + local p = packet.from_data( + _unhex('FF:FF:FF:FF:FF:FF').. + _unhex('52:54:00:01:01:01').. + _unhex('08:00').. + 'this is the payload'.. + _unhex('00:00:00:00') + ) + end, + }, +} \ No newline at end of file diff --git a/src/apps/intel/intel_app.lua b/src/apps/intel/intel_app.lua index 329c34f3a6..9c427004dd 100644 --- a/src/apps/intel/intel_app.lua +++ b/src/apps/intel/intel_app.lua @@ -115,6 +115,7 @@ function selftest () mq_sq('0000:05:00.0', '0000:8a:00.0') app.main({duration = 1, report={showlinks=true, showapps=false}}) + require ("test")('src/apps/intel/intel10g_t') end -- open two singlequeue drivers on both ends of the wire diff --git a/src/apps/intel/intel_t.lua b/src/apps/intel/intel_t.lua new file mode 100644 index 0000000000..74becbfd98 --- /dev/null +++ b/src/apps/intel/intel_t.lua @@ -0,0 +1,24 @@ +return { + sum = function() + assert(2+2 == 4, "if this fails, there's no hope") + end, + + mult = function() + assert(2*2 == 4.1, "missed it by that much") + end, + + strs = { + + concat = function() + assert('a'..'b' == 'ab', "the obvious thing") + end, + + repeated = function() + assert(3*'a' == 'aaa', "would this work?") + end, + + good_repeat = function() + assert(string.rep('a',3)=='aaa', "this should work") + end, + }, +} \ No newline at end of file diff --git a/src/core/config.lua b/src/core/config.lua index e7ed6751a9..05dd77e155 100644 --- a/src/core/config.lua +++ b/src/core/config.lua @@ -60,7 +60,7 @@ end -- Example: -- parse_app_arg("{ timeout = 5 * 10 }") => { timeout = 50 } function parse_app_arg (s) - print("s", type(s), s) +-- print("s", type(s), s) assert(type(s) == 'string') return loadstring("return " .. s)() end diff --git a/src/core/lib.lua b/src/core/lib.lua index 6c417e97ec..92baf3ef1e 100644 --- a/src/core/lib.lua +++ b/src/core/lib.lua @@ -277,27 +277,5 @@ function selftest () :match('^45.00.B6.7D.00.FA.40.00.40.11$'), "wrong hex dump") assert(hexundump('4500 B67D 00FA400040 11', 10) =='\x45\x00\xb6\x7d\x00\xFA\x40\x00\x40\x11', "wrong hex undump") - - local macA = new_mac('00-01-02-0a-0b-0c') - local macB = new_mac('0001020A0B0C') - local macC = new_mac('0A:0B:0C:00:01:02') - print ('macA', macA) - assert (tostring(macA) == '00:01:02:0A:0B:0C', "bad canonical MAC") - assert (macA == macB, "macA and macB should be equal") - assert (macA ~= macC, "macA and macC should be different") - assert (macA:subbits(0,31)==0x0a020100, "low A") - assert (macA:subbits(32,48)==0x0c0b, ("hi A (%X)"):format(macA:subbits(32,48))) - assert (macC:subbits(0,31)==0x000c0b0a, "low C") - assert (macC:subbits(32,48)==0x0201," hi C") - - local ndx_set = new_index_set(4, 'test ndx') - assert (string.format('%d/%s', ndx_set:add('a'))=='0/true', "indexes start with 0, and is new") - assert (string.format('%d/%s', ndx_set:add('b'))=='1/true', "second new index") - assert (string.format('%d/%s', ndx_set:add('c'))=='2/true', "third new") - assert (string.format('%d/%s', ndx_set:add('b'))=='1/false', "that's an old one") - assert (string.format('%d/%s', ndx_set:add('a'))=='0/false', "the very first one") - assert (string.format('%d/%s', ndx_set:add('A'))=='3/true', "almost, but new") - assert (string.format('%s/%s', pcall(ndx_set.add, ndx_set,'B')) - :match('^false/core/lib.lua:%d+: test ndx overflow'), 'should overflow') end diff --git a/src/core/packet_t.lua b/src/core/packet_t.lua new file mode 100644 index 0000000000..beceecbc3d --- /dev/null +++ b/src/core/packet_t.lua @@ -0,0 +1,143 @@ +local ffi = require("ffi") +local C = ffi.C +local memory = require "core.memory" +memory.allocate_RAM = C.malloc +memory.ram_to_io_addr = memory.virtual_to_physical +local buffer = require "core.buffer" +local packet = require "core.packet" + + +return { + from_data = function () + local p = packet.from_data('abcdefghijklmnopqrstuvwxyz') + assert (packet.tostring(p) == 'abcdefghijklmnopqrstuvwxyz') + end, + + add_iovec = { + + to_empty_packet = function () + local p = packet.allocate() + local b = buffer.from_data('abcdefghijklmnopqrstuvwxyz') + packet.add_iovec(p, b, 26, 0) + assert (p.niovecs == 1) + assert (p.length == 26) + assert (buffer.tostring(p.iovecs[0].buffer, 26) == 'abcdefghijklmnopqrstuvwxyz') + assert (packet.tostring(p) == 'abcdefghijklmnopqrstuvwxyz') + end, + + to_single_iovec_packet = function () + local p = packet.from_data('abcdefghijklm') + local b = buffer.from_data('nopqrstuvwxyz') + packet.add_iovec(p, b, 13, 0) + assert (p.niovecs == 2) + assert (p.length == 26) + assert (buffer.tostring(p.iovecs[0].buffer, 13) == 'abcdefghijklm') + assert (buffer.tostring(p.iovecs[1].buffer, 13) == 'nopqrstuvwxyz') + assert (packet.tostring(p) == 'abcdefghijklmnopqrstuvwxyz') + end, + }, + + prepend_iovec = { + + to_empty_packet = function () + local p = packet.allocate() + local b = buffer.from_data('abcdefghijklmnopqrstuvwxyz') + packet.prepend_iovec(p, b, 26, 0) + assert (p.niovecs == 1) + assert (p.length == 26) + assert (buffer.tostring(p.iovecs[0].buffer, 26) == 'abcdefghijklmnopqrstuvwxyz') + assert (packet.tostring(p) == 'abcdefghijklmnopqrstuvwxyz') + end, + + to_single_iovec_packet = function () + local p = packet.from_data('nopqrstuvwxyz') + local b = buffer.from_data('abcdefghijklm') + packet.prepend_iovec(p, b, 13, 0) + assert (p.niovecs == 2) + assert (p.length == 26) + assert (buffer.tostring(p.iovecs[0].buffer, 13) == 'abcdefghijklm') + assert (buffer.tostring(p.iovecs[1].buffer, 13) == 'nopqrstuvwxyz') + assert (packet.tostring(p) == 'abcdefghijklmnopqrstuvwxyz') + end, + }, + + coalesce = function () + local p = packet.from_data('abcdefghijklm') + local b = buffer.from_data('nopqrstuvwxyz') + packet.add_iovec(p, b, 13, 0) + assert (p.niovecs == 2) + assert (p.length == 26) + assert (packet.tostring(p) == 'abcdefghijklmnopqrstuvwxyz') + packet.coalesce(p) + assert (p.niovecs == 1) + assert (p.length == 26) + assert (packet.tostring(p) == 'abcdefghijklmnopqrstuvwxyz') + end, + + fill_data = { + + at_start = { + to_single_iovec_packet = function () + local p = packet.from_data('abcdefghijklmnopqrstuvwxyz') + packet.fill_data(p, '12345678') + assert (packet.tostring(p) == '12345678ijklmnopqrstuvwxyz') + end, + + to_first_of_multi_iovec_packet = function () + local p = packet.allocate() + packet.add_iovec(p, buffer.from_data('abcdefghijklm'), 13, 0) + packet.add_iovec(p, buffer.from_data('nopqrstuvwxyz'), 13, 0) + packet.fill_data(p, '12345678', 0) + assert (packet.tostring(p) == '12345678ijklmnopqrstuvwxyz') + end, + + to_two_of_multi_iovec_packet = function () + local p = packet.allocate() + packet.add_iovec(p, buffer.from_data('abcdefghijklm'), 13, 0) + packet.add_iovec(p, buffer.from_data('nopqrstuvwxyz'), 13, 0) + packet.fill_data(p, '12345678', 0) + assert (packet.tostring(p) == '12345678ijklmnopqrstuvwxyz') + end, + }, + at_offset = { + to_single_iovec_packet = function () + local p = packet.from_data('abcdefghijklmnopqrstuvwxyz') + packet.fill_data(p, '12345678', 3) + assert (packet.tostring(p) == 'abc12345678lmnopqrstuvwxyz') + end, + + to_first_of_multi_iovec_packet = function () + local p = packet.allocate() + packet.add_iovec(p, buffer.from_data('abcdefghijklm'), 13, 0) + packet.add_iovec(p, buffer.from_data('nopqrstuvwxyz'), 13, 0) + packet.fill_data(p, '12345678', 3) + assert (packet.tostring(p) == 'abc12345678lmnopqrstuvwxyz') + end, + + to_second_of_multi_iovec_packet = function () + local p = packet.allocate() + packet.add_iovec(p, buffer.from_data('abcdefghijklm'), 13, 0) + packet.add_iovec(p, buffer.from_data('nopqrstuvwxyz'), 13, 0) + packet.fill_data(p, '12345678', 15) + assert (packet.tostring(p) == 'abcdefghijklmno12345678xyz') + end, + + to_two_of_multi_iovec_packet = function () + local p = packet.allocate() + packet.add_iovec(p, buffer.from_data('abcdefghijklm'), 13, 0) + packet.add_iovec(p, buffer.from_data('nopqrstuvwxyz'), 13, 0) + packet.fill_data(p, '12345678', 9) + assert (packet.tostring(p) == 'abcdefghi12345678rstuvwxyz') + end, + + to_three_of_multi_iovec_packet = function () + local p = packet.allocate() + packet.add_iovec(p, buffer.from_data('abcdefghijk'), 11, 0) + packet.add_iovec(p, buffer.from_data('lmno'), 4, 0) + packet.add_iovec(p, buffer.from_data('pqrstuvwxyz'), 11, 0) + packet.fill_data(p, '12345678', 9) + assert (packet.tostring(p) == 'abcdefghi12345678rstuvwxyz') + end, + }, + }, +} \ No newline at end of file diff --git a/src/lib/index_set_t.lua b/src/lib/index_set_t.lua new file mode 100644 index 0000000000..b1a1dab263 --- /dev/null +++ b/src/lib/index_set_t.lua @@ -0,0 +1,18 @@ +local index_set = require "lib.index_set" + +local function _t(a, b) + return tostring(a)..'/'..tostring(b) +end + +return { + basic_handling = function () + local ndx_set = index_set:new(4, 'test ndx') + assert (_t(ndx_set:add('a'))=='0/true', "indexes start with 0, and is new") + assert (_t(ndx_set:add('b'))=='1/true', "second new index") + assert (_t(ndx_set:add('c'))=='2/true', "third new") + assert (_t(ndx_set:add('b'))=='1/false', "that's an old one") + assert (_t(ndx_set:add('a'))=='0/false', "the very first one") + assert (_t(ndx_set:add('A'))=='3/true', "almost, but new") + assert (_t(pcall(ndx_set.add, ndx_set,'B')) + :match('^false/lib/index_set.lua:%d+: test ndx overflow'), 'should overflow') end +} diff --git a/src/lib/macaddress_t.lua b/src/lib/macaddress_t.lua new file mode 100644 index 0000000000..2c110cc1ce --- /dev/null +++ b/src/lib/macaddress_t.lua @@ -0,0 +1,16 @@ +local macaddress = require "lib.macaddress" + +return { + basic_handling = function () + local macA = macaddress:new('00-01-02-0a-0b-0c') + local macB = macaddress:new('0001020A0B0C') + local macC = macaddress:new('0A:0B:0C:00:01:02') + assert (tostring(macA) == '00:01:02:0A:0B:0C', "bad canonical MAC") + assert (macA == macB, "macA and macB should be equal") + assert (macA ~= macC, "macA and macC should be different") + assert (macA:subbits(0,31)==0x0a020100, "low A") + assert (macA:subbits(32,48)==0x0c0b, ("hi A (%X)"):format(macA:subbits(32,48))) + assert (macC:subbits(0,31)==0x000c0b0a, "low C") + assert (macC:subbits(32,48)==0x0201," hi C") + end, +} diff --git a/src/test.lua b/src/test.lua new file mode 100644 index 0000000000..65d3052fb7 --- /dev/null +++ b/src/test.lua @@ -0,0 +1,66 @@ +local ffi = require("ffi") +local C = ffi.C +local main = require("core.main") + +local shortlogs = false +local results = {fails=0} + +local function show_log() + io.write(string.format('\n======= %d tests, %d failures\n', #results, results.fails)) + for _,log in ipairs(results) do + if #log.out > 0 then + io.write(log.ok and '[Ok]\t' or '[FAIL]\t', log.name, ':\n') + for _,line in ipairs(log.out) do + io.write(line, '\n') + end + io.write('------\n') + end + end +end + +local function log(name, ok, ...) + results[#results+1] = {name=name or '---', ok=ok, out={...}} + if not ok then results.fails = results.fails+1 end + return ok +end + +local function do_test(name, test, showlog) + if showlog and name ~= '' then io.write('======= ', name, '\n') end + local function gettb(err) + return debug.traceback(err, 2) + :gsub('\n[^\n]*xpcall\'%s*test.lua:.*$', '\n\t['..name..']') + end + + if type(test) == 'function' then -- perform the test +-- log(name, xpcall(test, gettb)) + io.write(log(name, xpcall(test, gettb)) and '[Ok]\t' or '[FAIL]\t', name, '\n') + + elseif type(test) == 'table' then -- test every non '_xxx' item + for k, v in pairs(test) do + if test.__setup then + if not log (nil, xpcall(test.__setup, gettb, test, k, v)) then return end + end + if string.sub(k, 1, 1) ~= '_' then + do_test(name..'.'..((type(k)=='string' or type(v)~='string') and k or v), v, showlog) + end + end + + elseif type(test) == 'string' then + if io.open(test..'.lua', 'r') then -- load test(s) from a file + do_test(name, assert(loadfile(test..'.lua'))()) + else -- last resort: a directory + for fn in io.popen('ls -1F "'..test..'" 2>/dev/null'):lines() do + if fn:match('_t%.lua$') then -- found a test file + do_test(name..'.'..fn:sub(1, -5), test..'/'..fn:sub(1, -5)) + + elseif fn:sub(-1) == '/' then -- subdirectory: recurse + do_test(name..'.'..fn:sub(1, -2), test..'/'..fn:sub(1, -2)) + end + end + end + end + if showlog and name ~= '' then show_log() end +end + +do_test('', main.parameters, true) +return function(tn) do_test(tn, tn, true) end \ No newline at end of file From 4cd4fdad7914e5fd52288ed4545ccf9957ab6efe Mon Sep 17 00:00:00 2001 From: Javier Guerra Date: Wed, 16 Apr 2014 04:46:42 -0500 Subject: [PATCH 2/4] documentation --- src/apps/intel/intel_t.lua | 24 ------------- src/core/buffer.lua | 33 +++++++++++++++--- src/core/memory.c | 4 +-- src/core/packet.h | 23 ++++++------ src/core/packet.lua | 66 +++++++++++++++++++++++------------ src/doc/genbook.sh | 39 +++++++++++++-------- src/doc/test.md | 61 ++++++++++++++++++++++++++++++++ src/lib/hardware/register.lua | 8 +++-- 8 files changed, 177 insertions(+), 81 deletions(-) delete mode 100644 src/apps/intel/intel_t.lua create mode 100644 src/doc/test.md diff --git a/src/apps/intel/intel_t.lua b/src/apps/intel/intel_t.lua deleted file mode 100644 index 74becbfd98..0000000000 --- a/src/apps/intel/intel_t.lua +++ /dev/null @@ -1,24 +0,0 @@ -return { - sum = function() - assert(2+2 == 4, "if this fails, there's no hope") - end, - - mult = function() - assert(2*2 == 4.1, "missed it by that much") - end, - - strs = { - - concat = function() - assert('a'..'b' == 'ab', "the obvious thing") - end, - - repeated = function() - assert(3*'a' == 'aaa', "would this work?") - end, - - good_repeat = function() - assert(string.rep('a',3)=='aaa', "this should work") - end, - }, -} \ No newline at end of file diff --git a/src/core/buffer.lua b/src/core/buffer.lua index b826c7b04f..7df7ac8aad 100644 --- a/src/core/buffer.lua +++ b/src/core/buffer.lua @@ -20,12 +20,14 @@ buffer_ptr_t = ffi.typeof("struct buffer *") -- This is used to return freed buffers to their devices. virtio_devices = {} --- Return a ready-to-use buffer, or nil if none is available. +--- ### Allocation + +--- Return a ready-to-use buffer, or nil if none is available. function allocate () return freelist.remove(buffers) or new_buffer() end --- Return a newly created buffer, or nil if none can be created. +--- Return a newly created buffer, or nil if none can be created. function new_buffer () assert(allocated < max, "out of buffers") allocated = allocated + 1 @@ -35,7 +37,7 @@ function new_buffer () return b end --- Free a buffer that is no longer in use. +--- Free a buffer that is no longer in use. function free (b) freelist.add(buffers, b) if b.origin.type == C.BUFFER_ORIGIN_VIRTIO then @@ -43,8 +45,8 @@ function free (b) end end --- Create buffers until at least N are ready for use. --- This is a way to pay the cost of allocating buffer memory in advance. +--- Create buffers until at least N are ready for use. +--- This is a way to pay the cost of allocating buffer memory in advance. function preallocate (n) while freelist.nfree(buffers) < n do free(new_buffer()) end end @@ -59,3 +61,24 @@ function delete_virtio_device (index) virtio_devices[index] = nil end +--- ### to/from binary strings + +--- copies data from a string to an allocated buffer +function fill_data (b, d, offset) + offset = offset or 0 + assert (offset+#d <= b.size, "can't fit on buffer") + ffi.copy (b.pointer + offset, d, math.min(#d, b.size-offset)) +end + +--- creates a buffer from a given binary string +function from_data (d) + local b = allocate() + local size = math.min(#d, b.size) + fill_data(b, d) + return b +end + +--- returns a string with buffer's content +function tostring(b, size) + return ffi.string(b.pointer, size or b.size) +end diff --git a/src/core/memory.c b/src/core/memory.c index c27de86f5e..2163a35853 100644 --- a/src/core/memory.c +++ b/src/core/memory.c @@ -9,8 +9,8 @@ /// ### HugeTLB page allocation -// Allocate a HugeTLB memory page of 'size' bytes. -// Return a pointer to the start of the page, or NULL on failure. +/// Allocate a HugeTLB memory page of 'size' bytes. +/// Return a pointer to the start of the page, or NULL on failure. void *allocate_huge_page(int size) { void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, diff --git a/src/core/packet.h b/src/core/packet.h index 185fb4f356..65e3f7c049 100644 --- a/src/core/packet.h +++ b/src/core/packet.h @@ -1,7 +1,7 @@ -// Buffers can be treated specially depending on their origin. -// -// For example, buffers belonging to Virtio devices need to be -// returned to the device when freed. +/// Buffers can be treated specially depending on their origin. +/// +/// For example, buffers belonging to Virtio devices need to be +/// returned to the device when freed. struct buffer_origin { enum buffer_origin_type { @@ -18,7 +18,7 @@ struct buffer_origin { } info; }; -// A buffer describes a piece of memory with known size and physical address. +/// A buffer describes a piece of memory with known size and physical address. struct buffer { char *pointer; // virtual address in this process uint64_t physical; // stable physical address @@ -26,7 +26,7 @@ struct buffer { struct buffer_origin origin; }; -// A packet_iovec describes a portion of a buffer. +/// A packet_iovec describes a portion of a buffer. struct packet_iovec { struct buffer *buffer; uint32_t offset; @@ -36,10 +36,10 @@ struct packet_iovec { // Maximum number of packet_iovec's per packet. enum { PACKET_IOVEC_MAX = 16 }; -// Packet info (metadata) for checksum and segmentation offload. -// -// This is intentionally bit-compatible with the Virtio structure -// 'virtio_net_hdr' because that seems a reasonable starting point. +/// Packet info (metadata) for checksum and segmentation offload. +/// +/// This is intentionally bit-compatible with the Virtio structure +/// 'virtio_net_hdr' because that seems a reasonable starting point. struct packet_info { uint8_t flags; // see below uint8_t gso_flags; // see below @@ -64,6 +64,9 @@ enum { PACKET_GSO_ECN = 0x80 // TCP has ECN set }; +/// Packet data structure. +/// +/// Holds a number of iovecs, reference counting and other housekeeping info struct packet { int32_t refcount; // How much "fuel" does this packet have left before it's dropped? diff --git a/src/core/packet.lua b/src/core/packet.lua index 72fd19e790..e70fa4204b 100644 --- a/src/core/packet.lua +++ b/src/core/packet.lua @@ -27,7 +27,7 @@ function allocate () return freelist.remove(packets_fl) or error("out of packets") end --- Append data to a packet. +--- Append data to a packet. function add_iovec (p, b, length, offset) if debug then assert(p.niovecs < C.PACKET_IOVEC_MAX, "packet iovec overflow") end offset = offset or 0 @@ -77,25 +77,9 @@ function coalesce (p) add_iovec(p, b, length) end --- fill's an allocated packet with data from a string -function fill_data (p, d, offset) - offset = offset or 0 - local iovec = p.iovecs[0] - assert (offset+#d <= iovec.length, "can't fit on first iovec") -- TODO: handle more iovecs - ffi.copy (iovec.buffer.pointer + iovec.offset + offset, d, #d) -end +--- ### Refcounting --- creates a packet from a given binary string -function from_data (d) - local p = allocate() - local b = buffer.allocate() - local size = math.min(#d, b.size) - add_iovec(p, b, size) - fill_data(p, d) - return p -end - --- Increase the reference count for packet p by n (default n=1). +--- Increase the reference count for packet p by n (default n=1). function ref (p, n) if p.refcount > 0 then p.refcount = p.refcount + (n or 1) @@ -103,8 +87,8 @@ function ref (p, n) return p end --- Decrease the reference count for packet p. --- The packet will be recycled if the reference count reaches 0. +--- Decrease the reference count for packet p. +--- The packet will be recycled if the reference count reaches 0. function deref (p, n) n = n or 1 if p.refcount > 0 then @@ -117,12 +101,12 @@ function deref (p, n) end end --- Tenured packets are not reused by defref(). +--- Tenured packets are not reused by defref(). function tenure (p) p.refcount = 0 end --- Free a packet and all of its buffers. +--- Free a packet and all of its buffers. function free (p) for i = 0, p.niovecs-1 do buffer.free(p.iovecs[i].buffer) @@ -133,6 +117,42 @@ function free (p) freelist.add(packets_fl, p) end +--- ### to/from binary strings + +--- Returns a Lua binary string with the whole contents of the packet +function tostring(p) + local out = {} + for i = 0, p.niovecs-1 do + local iovec = p.iovecs[i] + out[i] = ffi.string(iovec.buffer.pointer + iovec.offset, iovec.length) + end + return table.concat(out, '', 0) +end + +--- Writes data from a string into an existing packet at a specified offset +function fill_data (p, d, offset) + offset = offset or 0 + for i = 0, p.niovecs-1 do + local iovec = p.iovecs[i] + if iovec.length > offset then + ffi.copy(iovec.buffer.pointer + offset, d, math.min(#d, iovec.length - offset)) + d = d:sub(iovec.length - offset + 1) + if d == '' then return end + offset = 0 + else + offset = offset - iovec.length + end + end + error("didn't find given offset") +end + +--- Creates a packet from a given binary string +function from_data (d) + local p = allocate() + add_iovec(p, buffer.from_data(d), #d) + return p +end + function iovec_dump (iovec) local b = iovec.buffer local l = math.min(iovec.length, b.size-iovec.offset) diff --git a/src/doc/genbook.sh b/src/doc/genbook.sh index 20afebee2e..c388596474 100755 --- a/src/doc/genbook.sh +++ b/src/doc/genbook.sh @@ -14,30 +14,36 @@ cat < 1 { printf("; ") } { printf("%s", $0) } END { print("") }') # Memory -$(cat memory.md) +$(cat $(find . -name memory.md)) ## \`memory.c\`: Operating system support -$(cat obj/memory.c.md) +$(cat obj/core/memory.c.md) ## \`memory.lua\`: Allocate physical memory in Lua -$(cat obj/memory.lua.md) +$(cat obj/core/memory.lua.md) ## \`buffer.lua\`: Allocate packet buffers from a pool -$(cat obj/buffer.lua.md) +$(cat obj/core/buffer.lua.md) # PCI ## \`pci.c\`: Operating system support -$(cat obj/pci.c.md) +$(cat obj/lib/hardware/pci.c.md) ## \`pci.lua\`: PCI access in Lua -$(cat obj/pci.lua.md) +$(cat obj/lib/hardware/pci.lua.md) + +# Packets and datagrams +## \`packet.h\` low level structures +$(cat obj/core/packet.h.md) +## \`packet.lua\` basic handling operations +$(cat obj/core/packet.lua.md) # Hardware ethernet I/O ## Hardware device register access -$(cat obj/register.lua.md) +$(cat obj/lib/hardware/register.lua.md) ## Intel 82599 (10-Gigabit) ethernet device driver -$(cat obj/intel10g.lua.md) +$(cat obj/apps/intel/intel10g.lua.md) # Software ethernet I/O (virtio) $(cat virtio.md) ## \`virtio_vring.h\`: vring DMA ring buffer data structure -$(cat obj/virtio_vring.h.md) +$(cat obj/lib/virtio/virtio_vring.h.md) ## \`virtio_vhost.h\`: vhost Linux kernel `ioctl` data structures $(cat obj/virtio_vhost.h.md) ## \`virtio_vhost_client.c\`: Linux \`/dev/vhost-net\` vhost client @@ -52,21 +58,26 @@ $(cat kvm.md) $(cat obj/port.lua.md) ## \`hub2.lua\`: 2-port ethernet hub $(cat obj/hub2.lua.md) +$(cat core/buffer.lua.md) $(cat openstack.md) # *DRAFT* Library -## \`clib.h\`: Standard C function prototypes -$(cat obj/clib.h.md) +## \`lib.h\`: Standard C function prototypes +$(cat obj/core/lib.h.md) +## \`lib.c\`: +$(cat obj/core/lib.c.md) ## \`lib.lua\`: Lua library routines -$(cat obj/lib.lua.md) +$(cat obj/core/lib.lua.md) # Startup ## \`snabbswitch.c\`: C \`main()\` entry point -$(cat obj/snabbswitch.c.md) +$(cat obj/core/snabbswitch.c.md) ## \`main.lua\`: Lua entry point -$(cat obj/main.lua.md) +$(cat obj/core/main.lua.md) ## \`snabb_lib_init.c\`: Customized Lua initialization $(cat obj/snabb_lib_init.c.md) +$(cat test.md) + EOF diff --git a/src/doc/test.md b/src/doc/test.md new file mode 100644 index 0000000000..10849a4766 --- /dev/null +++ b/src/doc/test.md @@ -0,0 +1,61 @@ +# Testing + +A very simple test framework is included. Invoke it as `snabbswitch test [tests...]`, where the optional test specifications can be either Lua test files (without the `.lua` extension) or directories to be recursively walked, picking `*_t.lua` files. + +Each should return a table of named test functions and subtables. Test functions are tried if their names don't begin with a `_`. + +If a table (or subtable) defines a `__setup()` function, it is executed before each function and subtable in the same table, but not each function in a subtable. It receives four parameters: the table it's defined in, it's full name, the name of the function (or subtable) to be executed, and the function (or subtable) itself. + +The test code should be straightforward, finishing the function (or the testfile) is considered a passed test, failing with `error()` or `assert()` signals test failure. Any return value or error message is stored to be displayed in the final summary. + +Example: + + return { + sum = function () + assert(2+2 == 4, "if this fails, there's no hope") + end, + + mult = function () + assert(2*2 == 4.1, "missed it by that much") + end, + + strs = { + concat = function () + assert('a'..'b' == 'ab', "the obvious thing") + end, + + repeated = function () + assert(3*'a' == 'aaa', "would this work?") + end, + + good_repeat = function () + assert(string.rep('a',3)=='aaa', "this should work") + end, + }, + } + +output: + + ======= testsample_t + [Ok] testsample_t.sum + [FAIL] testsample_t.strs.repeated + [Ok] testsample_t.strs.concat + [Ok] testsample_t.strs.good_repeat + [FAIL] testsample_t.mult + + ======= 5 tests, 2 failures + [FAIL] testsample_t.strs.repeated: + testsample_t.lua:17: attempt to perform arithmetic on a string value + stack traceback: + testsample_t.lua:17: in function + [testsample_t.strs.repeated] + ------ + [FAIL] testsample_t.mult: + testsample_t.lua:7: missed it by that much + stack traceback: + [C]: in function 'assert' + testsample_t.lua:7: in function + [testsample_t.mult] + ------ + +note that there's no guaranteed order. I see it as a feature. diff --git a/src/lib/hardware/register.lua b/src/lib/hardware/register.lua index 7dbc803bc0..f01ce1f4b3 100644 --- a/src/lib/hardware/register.lua +++ b/src/lib/hardware/register.lua @@ -131,9 +131,11 @@ end --- The register objects become named entries in `table`. --- --- This is an example line for a register description: +--- --- TXDCTL 0x06028 +0x40*0..127 (RW) Transmit Descriptor Control --- --- and this is the grammar: +--- --- Register ::= Name Offset Indexing Mode Longname --- Name ::= --- Indexing ::= "-" @@ -155,9 +157,9 @@ function define (description, table, base_ptr, n) end end --- registers of the form '+0xXX*j..k' are converted to --- an array of registers. --- naïve implementation: actually create the whole array +--- registers of the form `+0xXX*j..k` are converted to +--- an array of registers. +--- naïve implementation: actually create the whole array function define_array (description, table, base_ptr) local pattern = [[ *(%S+) +(%S+) +(%S+) +(%S+) (.-) ]] From aeff0847a14f4ead066653fbd0f8b21c9535f755 Mon Sep 17 00:00:00 2001 From: Javier Guerra Date: Thu, 17 Apr 2014 22:31:35 -0500 Subject: [PATCH 3/4] packet manipulation --- src/apps/intel/intel10g.lua | 8 ++------ src/core/buffer.lua | 7 ++----- src/core/packet.lua | 40 ++++++++++++++++++++++++++++--------- src/core/packet_t.lua | 24 +++++++++++++++++----- 4 files changed, 54 insertions(+), 25 deletions(-) diff --git a/src/apps/intel/intel10g.lua b/src/apps/intel/intel10g.lua index 0b0dba04ac..ddc6e62718 100644 --- a/src/apps/intel/intel10g.lua +++ b/src/apps/intel/intel10g.lua @@ -168,21 +168,17 @@ end --- See datasheet section 7.1 "Inline Functions -- Transmit Functionality." -txdesc_flags = bits{eop=24,ifcs=25, dext=29, dtyp0=20, dtyp1=21} +txdesc_flags = bits{ifcs=25, dext=29, dtyp0=20, dtyp1=21} txdesc_flags_last = bits({eop=24}, txdesc_flags) function M_sf:transmit (p) - if p.niovecs > 1 then - packet.coalesce(p) - end for i = 0, p.niovecs - 1 do local iov = p.iovecs[i] local flags = (i + 1 < p.niovecs) and txdesc_flags or txdesc_flags_last self.txdesc[self.tdt].address = iov.buffer.physical + iov.offset self.txdesc[self.tdt].options = bit.bor(iov.length, flags, bit.lshift(p.length+0ULL, 46)) - self.txpackets[self.tdt] = p + self.txpackets[self.tdt] = packet.ref(p) self.tdt = (self.tdt + 1) % num_descriptors end - return packet.ref(p) end function M_sf:sync_transmit () diff --git a/src/core/buffer.lua b/src/core/buffer.lua index 7df7ac8aad..9071f0a342 100644 --- a/src/core/buffer.lua +++ b/src/core/buffer.lua @@ -61,16 +61,14 @@ function delete_virtio_device (index) virtio_devices[index] = nil end ---- ### to/from binary strings - ---- copies data from a string to an allocated buffer +-- fill's an allocated buffer with data from a string function fill_data (b, d, offset) offset = offset or 0 assert (offset+#d <= b.size, "can't fit on buffer") ffi.copy (b.pointer + offset, d, math.min(#d, b.size-offset)) end ---- creates a buffer from a given binary string +-- creates a buffer from a given binary string function from_data (d) local b = allocate() local size = math.min(#d, b.size) @@ -78,7 +76,6 @@ function from_data (d) return b end ---- returns a string with buffer's content function tostring(b, size) return ffi.string(b.pointer, size or b.size) end diff --git a/src/core/packet.lua b/src/core/packet.lua index e70fa4204b..6986c4b412 100644 --- a/src/core/packet.lua +++ b/src/core/packet.lua @@ -77,7 +77,29 @@ function coalesce (p) add_iovec(p, b, length) end ---- ### Refcounting +-- fill's an allocated packet with data from a string +function fill_data (p, d, offset) + offset = offset or 0 + for i = 0, p.niovecs-1 do + local iovec = p.iovecs[i] + if iovec.length > offset then + ffi.copy(iovec.buffer.pointer + offset, d, math.min(#d, iovec.length - offset)) + d = d:sub(iovec.length - offset + 1) + if d == '' then return end + offset = 0 + else + offset = offset - iovec.length + end + end + error("didn't find given offset") +end + +-- creates a packet from a given binary string +function from_data (d) + local p = allocate() + add_iovec(p, buffer.from_data(d), #d) + return p +end --- Increase the reference count for packet p by n (default n=1). function ref (p, n) @@ -117,16 +139,13 @@ function free (p) freelist.add(packets_fl, p) end ---- ### to/from binary strings - ---- Returns a Lua binary string with the whole contents of the packet function tostring(p) local out = {} - for i = 0, p.niovecs-1 do - local iovec = p.iovecs[i] + for i = 1, p.niovecs do + local iovec = p.iovecs[i-1] out[i] = ffi.string(iovec.buffer.pointer + iovec.offset, iovec.length) end - return table.concat(out, '', 0) + return table.concat(out, '') end --- Writes data from a string into an existing packet at a specified offset @@ -147,9 +166,12 @@ function fill_data (p, d, offset) end --- Creates a packet from a given binary string -function from_data (d) +function from_data (...) local p = allocate() - add_iovec(p, buffer.from_data(d), #d) + for i = 1, select('#', ...) do + local d = select(i, ...) + add_iovec(p, buffer.from_data(d), #d) + end return p end diff --git a/src/core/packet_t.lua b/src/core/packet_t.lua index beceecbc3d..96cd585ccb 100644 --- a/src/core/packet_t.lua +++ b/src/core/packet_t.lua @@ -8,11 +8,25 @@ local packet = require "core.packet" return { - from_data = function () - local p = packet.from_data('abcdefghijklmnopqrstuvwxyz') - assert (packet.tostring(p) == 'abcdefghijklmnopqrstuvwxyz') - end, - + from_data = { + no_parts = function () + local p = packet.from_data() + assert (p.niovecs == 0) + assert (packet.tostring(p) == '') + end, + + single_part = function () + local p = packet.from_data('abcdefghijklmnopqrstuvwxyz') + assert (p.niovecs == 1) + assert (packet.tostring(p) == 'abcdefghijklmnopqrstuvwxyz') + end, + + multiple_parts = function () + local p = packet.from_data('abcdef','ghijklmnopqr','stuvwx','yz') + assert (p.niovecs == 4) + assert (packet.tostring(p) == 'abcdefghijklmnopqrstuvwxyz') + end, + }, add_iovec = { to_empty_packet = function () From a1b16d0c758590955597bb0898b08091736e2d4b Mon Sep 17 00:00:00 2001 From: Javier Guerra Date: Sat, 19 Apr 2014 02:39:42 -0500 Subject: [PATCH 4/4] fix indentation --- src/core/main.lua | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/core/main.lua b/src/core/main.lua index d130df9536..45e256e68d 100644 --- a/src/core/main.lua +++ b/src/core/main.lua @@ -40,28 +40,28 @@ function main () local i = 1 while i <= #args do if args[i] == '-l' and i < #args then - require(args[i+1]) - i = i + 2 + require(args[i+1]) + i = i + 2 elseif args[i] == '-t' and i < #args then require(args[i+1]).selftest() i = i + 2 elseif args[i] == '-e' and i < #args then - local thunk, error = loadstring(args[i+1]) - if thunk then thunk() else print(error) end - i = i + 2 + local thunk, error = loadstring(args[i+1]) + if thunk then thunk() else print(error) end + i = i + 2 elseif args[i] == '-d' then - debug_on_error = true - i = i + 1 + debug_on_error = true + i = i + 1 elseif (args[i]):match("-jp") then - local pargs, poutput = (args[i]):gmatch("-jp=(%w*),?(.*)")() - if poutput == '' then poutput = nil end - require("jit.p").start(pargs, poutput) - profiling = true - i = i + 1 + local pargs, poutput = (args[i]):gmatch("-jp=(%w*),?(.*)")() + if poutput == '' then poutput = nil end + require("jit.p").start(pargs, poutput) + profiling = true + i = i + 1 elseif args[i] == '-jdump' and i < #args then - local jit_dump = require "jit.dump" - jit_dump.start("", args[i+1]) - i = i + 2 + local jit_dump = require "jit.dump" + jit_dump.start("", args[i+1]) + i = i + 2 elseif i <= #args then -- Syntax: [args...] local module = args[i] @@ -73,8 +73,8 @@ function main () require(module) exit(0) else - print(usage) - os.exit(1) + print(usage) + os.exit(1) end end exit(0)