From 097ff4b8b28f56c08e065e03cd9ce21257a2b270 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Sat, 30 Aug 2014 10:13:08 -0300 Subject: [PATCH 01/11] unittest: convert garray_spec.moon to lua --- test/unit/garray_spec.lua | 391 +++++++++++++++++++++++++++++++++++++ test/unit/garray_spec.moon | 271 ------------------------- 2 files changed, 391 insertions(+), 271 deletions(-) create mode 100644 test/unit/garray_spec.lua delete mode 100644 test/unit/garray_spec.moon diff --git a/test/unit/garray_spec.lua b/test/unit/garray_spec.lua new file mode 100644 index 00000000000000..ab38176c41672d --- /dev/null +++ b/test/unit/garray_spec.lua @@ -0,0 +1,391 @@ +local helpers = require("test.unit.helpers") + +local cimport = helpers.cimport +local internalize = helpers.internalize +local eq = helpers.eq +local neq = helpers.neq +local ffi = helpers.ffi +local lib = helpers.lib +local cstr = helpers.cstr +local to_cstr = helpers.to_cstr +local NULL = helpers.NULL + +local garray = cimport('./src/nvim/garray.h') + +-- define a basic interface to garray. We could make it a lot nicer by +-- constructing a moonscript class wrapper around garray. It could for +-- example associate ga_clear_strings to the underlying garray cdata if the +-- garray is a string array. But for now I estimate that that kind of magic +-- might make testing less "transparant" (i.e.: the interface would become +-- quite different as to how one would use it from C. + +-- accessors +function ga_len(garr) + return garr[0].ga_len +end + +function ga_maxlen(garr) + return garr[0].ga_maxlen +end + +function ga_itemsize(garr) + return garr[0].ga_itemsize +end + +function ga_growsize(garr) + return garr[0].ga_growsize +end + +function ga_data(garr) + return garr[0].ga_data +end + +-- derived accessors +function ga_size(garr) + return ga_len(garr) * ga_itemsize(garr) +end + +function ga_maxsize(garr) + return ga_maxlen(garr) * ga_itemsize(garr) +end + +function ga_data_as_bytes(garr) + return ffi.cast('uint8_t *', ga_data(garr)) +end + +function ga_data_as_strings(garr) + return ffi.cast('char **', ga_data(garr)) +end + +function ga_data_as_ints(garr) + return ffi.cast('int *', ga_data(garr)) +end + +-- garray manipulation +function ga_init(garr, itemsize, growsize) + return garray.ga_init(garr, itemsize, growsize) +end + +function ga_clear(garr) + return garray.ga_clear(garr) +end + +function ga_clear_strings(garr) + assert.is_true(ga_itemsize(garr) == ffi.sizeof('char *')) + return garray.ga_clear_strings(garr) +end + +function ga_grow(garr, n) + return garray.ga_grow(garr, n) +end + +function ga_concat(garr, str) + return garray.ga_concat(garr, to_cstr(str)) +end + +function ga_append(garr, b) + if type(b) == 'string' then + return garray.ga_append(garr, string.byte(b)) + else + return garray.ga_append(garr, b) + end +end + +function ga_concat_strings(garr) + return internalize(garray.ga_concat_strings(garr)) +end + +function ga_concat_strings_sep(garr, sep) + return internalize(garray.ga_concat_strings_sep(garr, to_cstr(sep))) +end + +function ga_remove_duplicate_strings(garr) + return garray.ga_remove_duplicate_strings(garr) +end + +-- derived manipulators +function ga_set_len(garr, len) + assert.is_true(len <= ga_maxlen(garr)) + garr[0].ga_len = len +end + +function ga_inc_len(garr, by) + return ga_set_len(garr, ga_len(garr) + 1) +end + +-- custom append functions +-- not the C ga_append, which only works for bytes +function ga_append_int(garr, it) + assert.is_true(ga_itemsize(garr) == ffi.sizeof('int')) + ga_grow(garr, 1) + local data = ga_data_as_ints(garr) + data[ga_len(garr)] = it + return ga_inc_len(garr, 1) +end + +function ga_append_string(garr, it) + assert.is_true(ga_itemsize(garr) == ffi.sizeof('char *')) + -- make a non-garbage collected string and copy the lua string into it, + -- TODO(aktau): we should probably call xmalloc here, though as long as + -- xmalloc is based on malloc it should work. + local mem = ffi.C.malloc(string.len(it) + 1) + ffi.copy(mem, it) + ga_grow(garr, 1) + local data = ga_data_as_strings(garr) + data[ga_len(garr)] = mem + return ga_inc_len(garr, 1) +end + +function ga_append_strings(garr, ...) + local prevlen = ga_len(garr) + local len = select('#', ...) + for i = 1, len do + ga_append_string(garr, select(i, ...)) + end + return eq(prevlen + len, ga_len(garr)) +end + +function ga_append_ints(garr, ...) + local prevlen = ga_len(garr) + local len = select('#', ...) + for i = 1, len do + ga_append_int(garr, select(i, ...)) + end + return eq(prevlen + len, ga_len(garr)) +end + +-- enhanced constructors +local garray_ctype = ffi.typeof('garray_T[1]') +function new_garray() + local garr = garray_ctype() + return ffi.gc(garr, ga_clear) +end + +function new_string_garray() + local garr = garray_ctype() + ga_init(garr, ffi.sizeof("char_u *"), 1) + return ffi.gc(garr, ga_clear_strings) +end + +function randomByte() + return ffi.cast('uint8_t', math.random(0, 255)) +end + +-- scramble the data in a garray +function ga_scramble(garr) + local size, bytes = ga_size(garr), ga_data_as_bytes(garr) + for i = 0, size - 1 do + bytes[i] = randomByte() + end +end + +describe('garray', function() + local itemsize = 14 + local growsize = 95 + + describe('ga_init', function() + it('initializes the values of the garray', function() + local garr = new_garray() + ga_init(garr, itemsize, growsize) + eq(0, ga_len(garr)) + eq(0, ga_maxlen(garr)) + eq(growsize, ga_growsize(garr)) + eq(itemsize, ga_itemsize(garr)) + eq(NULL, ga_data(garr)) + end) + end) + + describe('ga_grow', function() + local new_and_grow + function new_and_grow(itemsize, growsize, req) + local garr = new_garray() + ga_init(garr, itemsize, growsize) + eq(0, ga_size(garr)) -- should be 0 at first + eq(NULL, ga_data(garr)) -- should be NULL + ga_grow(garr, req) -- add space for `req` items + return garr + end + + it('grows by growsize items if num < growsize', function() + itemsize = 16 + growsize = 4 + local grow_by = growsize - 1 + local garr = new_and_grow(itemsize, growsize, grow_by) + neq(NULL, ga_data(garr)) -- data should be a ptr to memory + eq(growsize, ga_maxlen(garr)) -- we requested LESS than growsize, so... + end) + + it('grows by num items if num > growsize', function() + itemsize = 16 + growsize = 4 + local grow_by = growsize + 1 + local garr = new_and_grow(itemsize, growsize, grow_by) + neq(NULL, ga_data(garr)) -- data should be a ptr to memory + eq(grow_by, ga_maxlen(garr)) -- we requested MORE than growsize, so... + end) + + it('does not grow when nothing is requested', function() + local garr = new_and_grow(16, 4, 0) + eq(NULL, ga_data(garr)) + eq(0, ga_maxlen(garr)) + end) + end) + + describe('ga_clear', function() + it('clears an already allocated array', function() + -- allocate and scramble an array + local garr = garray_ctype() + ga_init(garr, itemsize, growsize) + ga_grow(garr, 4) + ga_set_len(garr, 4) + ga_scramble(garr) + + -- clear it and check + ga_clear(garr) + eq(NULL, ga_data(garr)) + eq(0, ga_maxlen(garr)) + eq(0, ga_len(garr)) + end) + end) + + describe('ga_append', function() + it('can append bytes', function() + -- this is the actual ga_append, the others are just emulated lua + -- versions + local garr = new_garray() + ga_init(garr, ffi.sizeof("uint8_t"), 1) + ga_append(garr, 'h') + ga_append(garr, 'e') + ga_append(garr, 'l') + ga_append(garr, 'l') + ga_append(garr, 'o') + ga_append(garr, 0) + local bytes = ga_data_as_bytes(garr) + eq('hello', ffi.string(bytes)) + end) + + it('can append integers', function() + local garr = new_garray() + ga_init(garr, ffi.sizeof("int"), 1) + local input = { + -20, + 94, + 867615, + 90927, + 86 + } + ga_append_ints(garr, unpack(input)) + local ints = ga_data_as_ints(garr) + for i = 0, #input - 1 do + eq(input[i + 1], ints[i]) + end + end) + + it('can append strings to a growing array of strings', function() + local garr = new_string_garray() + local input = { + "some", + "str", + "\r\n\r●●●●●●,,,", + "hmm", + "got it" + } + ga_append_strings(garr, unpack(input)) + -- check that we can get the same strings out of the array + local strings = ga_data_as_strings(garr) + for i = 0, #input - 1 do + eq(input[i + 1], ffi.string(strings[i])) + end + end) + end) + + describe('ga_concat', function() + it('concatenates the parameter to the growing byte array', function() + local garr = new_garray() + ga_init(garr, ffi.sizeof("char"), 1) + local str = "ohwell●●" + local loop = 5 + for i = 1, loop do + ga_concat(garr, str) + end + + -- ga_concat does NOT append the NUL in the src string to the + -- destination, you have to do that manually by calling something like + -- ga_append(gar, '\0'). I'ts always used like that in the vim + -- codebase. I feel that this is a bit of an unnecesesary + -- micro-optimization. + ga_append(garr, 0) + local result = ffi.string(ga_data_as_bytes(garr)) + eq(string.rep(str, loop), result) + end) + end) + + function test_concat_fn(input, fn, sep) + local garr = new_string_garray() + ga_append_strings(garr, unpack(input)) + if sep == nil then + eq(table.concat(input, ','), fn(garr)) + else + eq(table.concat(input, sep), fn(garr, sep)) + end + end + + describe('ga_concat_strings', function() + it('returns an empty string when concatenating an empty array', function() + test_concat_fn({ }, ga_concat_strings) + end) + + it('can concatenate a non-empty array', function() + test_concat_fn({ + 'oh', + 'my', + 'neovim' + }, ga_concat_strings) + end) + end) + + describe('ga_concat_strings_sep', function() + it('returns an empty string when concatenating an empty array', function() + test_concat_fn({ }, ga_concat_strings_sep, '---') + end) + + it('can concatenate a non-empty array', function() + local sep = '-●●-' + test_concat_fn({ + 'oh', + 'my', + 'neovim' + }, ga_concat_strings_sep, sep) + end) + end) + + describe('ga_remove_duplicate_strings', function() + it('sorts and removes duplicate strings', function() + local garr = new_string_garray() + local input = { + 'ccc', + 'aaa', + 'bbb', + 'ddd●●', + 'aaa', + 'bbb', + 'ccc', + 'ccc', + 'ddd●●' + } + local sorted_dedup_input = { + 'aaa', + 'bbb', + 'ccc', + 'ddd●●' + } + ga_append_strings(garr, unpack(input)) + ga_remove_duplicate_strings(garr) + eq(#sorted_dedup_input, ga_len(garr)) + local strings = ga_data_as_strings(garr) + for i = 0, #sorted_dedup_input - 1 do + eq(sorted_dedup_input[i + 1], ffi.string(strings[i])) + end + end) + end) +end) diff --git a/test/unit/garray_spec.moon b/test/unit/garray_spec.moon deleted file mode 100644 index 5d4dbe690cdfe5..00000000000000 --- a/test/unit/garray_spec.moon +++ /dev/null @@ -1,271 +0,0 @@ -{:cimport, :internalize, :eq, :neq, :ffi, :lib, :cstr, :to_cstr, :NULL} = require 'test.unit.helpers' - -garray = cimport './src/nvim/garray.h' - --- define a basic interface to garray. We could make it a lot nicer by --- constructing a moonscript class wrapper around garray. It could for --- example associate ga_clear_strings to the underlying garray cdata if the --- garray is a string array. But for now I estimate that that kind of magic --- might make testing less "transparant" (i.e.: the interface would become --- quite different as to how one would use it from C. - --- accessors -ga_len = (garr) -> - garr[0].ga_len -ga_maxlen = (garr) -> - garr[0].ga_maxlen -ga_itemsize = (garr) -> - garr[0].ga_itemsize -ga_growsize = (garr) -> - garr[0].ga_growsize -ga_data = (garr) -> - garr[0].ga_data - --- derived accessors -ga_size = (garr) -> - ga_len(garr) * ga_itemsize(garr) -ga_maxsize = (garr) -> - ga_maxlen(garr) * ga_itemsize(garr) -ga_data_as_bytes = (garr) -> - ffi.cast('uint8_t *', ga_data(garr)) -ga_data_as_strings = (garr) -> - ffi.cast('char **', ga_data(garr)) -ga_data_as_ints = (garr) -> - ffi.cast('int *', ga_data(garr)) - --- garray manipulation -ga_init = (garr, itemsize, growsize) -> - garray.ga_init(garr, itemsize, growsize) -ga_clear = (garr) -> - garray.ga_clear(garr) -ga_clear_strings = (garr) -> - assert.is_true(ga_itemsize(garr) == ffi.sizeof('char *')) - garray.ga_clear_strings(garr) -ga_grow = (garr, n) -> - garray.ga_grow(garr, n) -ga_concat = (garr, str) -> - garray.ga_concat(garr, to_cstr(str)) -ga_append = (garr, b) -> - if type(b) == 'string' - garray.ga_append(garr, string.byte(b)) - else - garray.ga_append(garr, b) -ga_concat_strings = (garr) -> - internalize(garray.ga_concat_strings(garr)) -ga_concat_strings_sep = (garr, sep) -> - internalize(garray.ga_concat_strings_sep(garr, to_cstr(sep))) -ga_remove_duplicate_strings = (garr) -> - garray.ga_remove_duplicate_strings(garr) - --- derived manipulators -ga_set_len = (garr, len) -> - assert.is_true(len <= ga_maxlen(garr)) - garr[0].ga_len = len -ga_inc_len = (garr, by) -> - ga_set_len(garr, ga_len(garr) + 1) - --- custom append functions --- not the C ga_append, which only works for bytes -ga_append_int = (garr, it) -> - assert.is_true(ga_itemsize(garr) == ffi.sizeof('int')) - - ga_grow(garr, 1) - data = ga_data_as_ints(garr) - data[ga_len(garr)] = it - ga_inc_len(garr, 1) -ga_append_string = (garr, it) -> - assert.is_true(ga_itemsize(garr) == ffi.sizeof('char *')) - - -- make a non-garbage collected string and copy the lua string into it, - -- TODO(aktau): we should probably call xmalloc here, though as long as - -- xmalloc is based on malloc it should work. - mem = ffi.C.malloc(string.len(it) + 1) - ffi.copy(mem, it) - - ga_grow(garr, 1) - data = ga_data_as_strings(garr) - data[ga_len(garr)] = mem - ga_inc_len(garr, 1) -ga_append_strings = (garr, ...) -> - prevlen = ga_len(garr) - len = select('#', ...) - for i = 1, len - ga_append_string(garr, select(i, ...)) - eq prevlen + len, ga_len(garr) -ga_append_ints = (garr, ...) -> - prevlen = ga_len(garr) - len = select('#', ...) - for i = 1, len - ga_append_int(garr, select(i, ...)) - eq prevlen + len, ga_len(garr) - --- enhanced constructors -garray_ctype = ffi.typeof('garray_T[1]') -new_garray = -> - garr = garray_ctype() - ffi.gc(garr, ga_clear) -new_string_garray = -> - garr = garray_ctype() - ga_init(garr, ffi.sizeof("char_u *"), 1) - ffi.gc(garr, ga_clear_strings) - -randomByte = -> - ffi.cast('uint8_t', math.random(0, 255)) - --- scramble the data in a garray -ga_scramble = (garr) -> - size, bytes = ga_size(garr), ga_data_as_bytes(garr) - - for i = 0, size - 1 - bytes[i] = randomByte() - -describe 'garray', -> - itemsize = 14 - growsize = 95 - - describe 'ga_init', -> - it 'initializes the values of the garray', -> - garr = new_garray() - ga_init(garr, itemsize, growsize) - eq 0, ga_len(garr) - eq 0, ga_maxlen(garr) - eq growsize, ga_growsize(garr) - eq itemsize, ga_itemsize(garr) - eq NULL, ga_data(garr) - - describe 'ga_grow', -> - new_and_grow = (itemsize, growsize, req) -> - garr = new_garray() - ga_init(garr, itemsize, growsize) - - eq 0, ga_size(garr) -- should be 0 at first - eq NULL, ga_data(garr) -- should be NULL - ga_grow(garr, req) -- add space for `req` items - - garr - - it 'grows by growsize items if num < growsize', -> - itemsize = 16 - growsize = 4 - grow_by = growsize - 1 - garr = new_and_grow(itemsize, growsize, grow_by) - neq NULL, ga_data(garr) -- data should be a ptr to memory - eq growsize, ga_maxlen(garr) -- we requested LESS than growsize, so... - - it 'grows by num items if num > growsize', -> - itemsize = 16 - growsize = 4 - grow_by = growsize + 1 - garr = new_and_grow(itemsize, growsize, grow_by) - neq NULL, ga_data(garr) -- data should be a ptr to memory - eq grow_by, ga_maxlen(garr) -- we requested MORE than growsize, so... - - it 'does not grow when nothing is requested', -> - garr = new_and_grow(16, 4, 0) - eq NULL, ga_data(garr) - eq 0, ga_maxlen(garr) - - describe 'ga_clear', -> - it 'clears an already allocated array', -> - -- allocate and scramble an array - garr = garray_ctype() - ga_init(garr, itemsize, growsize) - ga_grow(garr, 4) - ga_set_len(garr, 4) - ga_scramble(garr) - - -- clear it and check - ga_clear(garr) - eq NULL, ga_data(garr) - eq 0, ga_maxlen(garr) - eq 0, ga_len(garr) - - describe 'ga_append', -> - it 'can append bytes', -> - -- this is the actual ga_append, the others are just emulated lua - -- versions - garr = new_garray() - ga_init(garr, ffi.sizeof("uint8_t"), 1) - ga_append(garr, 'h') - ga_append(garr, 'e') - ga_append(garr, 'l') - ga_append(garr, 'l') - ga_append(garr, 'o') - ga_append(garr, 0) - bytes = ga_data_as_bytes(garr) - eq 'hello', ffi.string(bytes) - - it 'can append integers', -> - garr = new_garray() - ga_init(garr, ffi.sizeof("int"), 1) - input = {-20, 94, 867615, 90927, 86} - ga_append_ints(garr, unpack(input)) - - ints = ga_data_as_ints(garr) - for i = 0, #input - 1 - eq input[i+1], ints[i] - - it 'can append strings to a growing array of strings', -> - garr = new_string_garray() - input = {"some", "str", "\r\n\r●●●●●●,,,", "hmm", "got it"} - ga_append_strings(garr, unpack(input)) - - -- check that we can get the same strings out of the array - strings = ga_data_as_strings(garr) - for i = 0, #input - 1 - eq input[i+1], ffi.string(strings[i]) - - describe 'ga_concat', -> - it 'concatenates the parameter to the growing byte array', -> - garr = new_garray() - ga_init(garr, ffi.sizeof("char"), 1) - - str = "ohwell●●" - loop = 5 - for i = 1, loop - ga_concat(garr, str) - - -- ga_concat does NOT append the NUL in the src string to the - -- destination, you have to do that manually by calling something like - -- ga_append(gar, '\0'). I'ts always used like that in the vim - -- codebase. I feel that this is a bit of an unnecesesary - -- micro-optimization. - ga_append(garr, 0) - - result = ffi.string(ga_data_as_bytes(garr)) - eq string.rep(str, loop), result - - test_concat_fn = (input, fn, sep) -> - garr = new_string_garray() - ga_append_strings(garr, unpack(input)) - if sep == nil - eq table.concat(input, ','), fn(garr) - else - eq table.concat(input, sep), fn(garr, sep) - - describe 'ga_concat_strings', -> - it 'returns an empty string when concatenating an empty array', -> - test_concat_fn({}, ga_concat_strings) - it 'can concatenate a non-empty array', -> - test_concat_fn({'oh', 'my', 'neovim'}, ga_concat_strings) - - describe 'ga_concat_strings_sep', -> - it 'returns an empty string when concatenating an empty array', -> - test_concat_fn({}, ga_concat_strings_sep, '---') - it 'can concatenate a non-empty array', -> - sep = '-●●-' - test_concat_fn({'oh', 'my', 'neovim'}, ga_concat_strings_sep, sep) - - describe 'ga_remove_duplicate_strings', -> - it 'sorts and removes duplicate strings', -> - garr = new_string_garray() - input = {'ccc', 'aaa', 'bbb', 'ddd●●', 'aaa', 'bbb', 'ccc', 'ccc', 'ddd●●'} - sorted_dedup_input = {'aaa', 'bbb', 'ccc', 'ddd●●'} - - ga_append_strings(garr, unpack(input)) - ga_remove_duplicate_strings(garr) - eq #sorted_dedup_input, ga_len(garr) - - strings = ga_data_as_strings(garr) - for i = 0, #sorted_dedup_input - 1 - eq sorted_dedup_input[i+1], ffi.string(strings[i]) From dcda179e6a49a6bc48b0b077ef3c1b58a3abcaed Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Sat, 30 Aug 2014 10:35:42 -0300 Subject: [PATCH 02/11] unittest: convert helpers.moon to lua --- test/unit/helpers.lua | 155 +++++++++++++++++++++++++++++++++++++++++ test/unit/helpers.moon | 131 ---------------------------------- 2 files changed, 155 insertions(+), 131 deletions(-) create mode 100644 test/unit/helpers.lua delete mode 100644 test/unit/helpers.moon diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua new file mode 100644 index 00000000000000..5ad0766d61a522 --- /dev/null +++ b/test/unit/helpers.lua @@ -0,0 +1,155 @@ +local ffi = require('ffi') +local lpeg = require('lpeg') +local formatc = require('test.unit.formatc') +local Set = require('test.unit.set') +local Preprocess = require('test.unit.preprocess') +local Paths = require('test.config.paths') + +-- add some standard header locations +for i, p in ipairs(Paths.include_paths) do + Preprocess.add_to_include_path(p) +end + +-- load neovim shared library +local libnvim = ffi.load(Paths.test_libnvim_path) + +function trim(s) + return s:match('^%s*(.*%S)') or '' +end + +-- a Set that keeps around the lines we've already seen +if cdefs == nil then + cdefs = Set:new() +end + +if imported == nil then + imported = Set:new() +end + +-- some things are just too complex for the LuaJIT C parser to digest. We +-- usually don't need them anyway. +function filter_complex_blocks(body) + local result = {} + + for line in body:gmatch("[^\r\n]+") do + if not (string.find(line, "(^)", 1, true) ~= nil or + string.find(line, "_ISwupper", 1, true)) then + result[#result + 1] = line + end + end + + return table.concat(result, "\n") +end + +-- use this helper to import C files, you can pass multiple paths at once, +-- this helper will return the C namespace of the nvim library. +-- cimport = (path) -> +function cimport(...) + local paths = {} + local args = {...} + + -- filter out paths we've already imported + for i = 1, #args do + local path = args[i] + if not imported:contains(path) then + paths[#paths + 1] = path + end + end + + for i = 1, #paths do + imported:add(paths[i]) + end + + if #paths == 0 then + return libnvim + end + + -- preprocess the header + local stream = Preprocess.preprocess_stream(unpack(paths)) + local body = stream:read("*a") + stream:close() + + -- format it (so that the lines are "unique" statements), also filter out + -- Objective-C blocks + body = formatc(body) + body = filter_complex_blocks(body) + + -- add the formatted lines to a set + local new_cdefs = Set:new() + for line in body:gmatch("[^\r\n]+") do + new_cdefs:add(trim(line)) + end + + -- subtract the lines we've already imported from the new lines, then add + -- the new unique lines to the old lines (so they won't be imported again) + new_cdefs:diff(cdefs) + cdefs:union(new_cdefs) + + if new_cdefs:size() == 0 then + -- if there's no new lines, just return + return libnvim + end + + -- request a sorted version of the new lines (same relative order as the + -- original preprocessed file) and feed that to the LuaJIT ffi + local new_lines = new_cdefs:to_table() + ffi.cdef(table.concat(new_lines, "\n")) + + return libnvim +end + +function cppimport(path) + return cimport(Paths.test_include_path .. '/' .. path) +end + +cimport('./src/nvim/types.h') + +-- take a pointer to a C-allocated string and return an interned +-- version while also freeing the memory +function internalize(cdata, len) + ffi.gc(cdata, ffi.C.free) + return ffi.string(cdata, len) +end + +local cstr = ffi.typeof('char[?]') +function to_cstr(string) + return cstr((string.len(string)) + 1, string) +end + +-- initialize some global variables, this is still necessary to unit test +-- functions that rely on global state. +function vim_init() + if vim_init_called ~= nil then + return + end + -- import os_unix.h for mch_early_init(), which initializes some globals + local os = cimport('./src/nvim/os_unix.h') + os.mch_early_init() + vim_init_called = true +end + +-- C constants. +local NULL = ffi.cast('void*', 0) + +local OK = 1 +local FAIL = 0 + +return { + cimport = cimport, + cppimport = cppimport, + internalize = internalize, + eq = function(expected, actual) + return assert.are.same(expected, actual) + end, + neq = function(expected, actual) + return assert.are_not.same(expected, actual) + end, + ffi = ffi, + lib = libnvim, + cstr = cstr, + to_cstr = to_cstr, + vim_init = vim_init, + NULL = NULL, + OK = OK, + FAIL = FAIL +} diff --git a/test/unit/helpers.moon b/test/unit/helpers.moon deleted file mode 100644 index f533b9a1c62514..00000000000000 --- a/test/unit/helpers.moon +++ /dev/null @@ -1,131 +0,0 @@ -ffi = require 'ffi' -lpeg = require 'lpeg' -formatc = require 'test.unit.formatc' -Set = require 'test.unit.set' -Preprocess = require 'test.unit.preprocess' -Paths = require 'test.config.paths' - --- add some standard header locations -for i,p in ipairs(Paths.include_paths) - Preprocess.add_to_include_path(p) - --- load neovim shared library -libnvim = ffi.load Paths.test_libnvim_path - -trim = (s) -> - s\match'^%s*(.*%S)' or '' - --- a Set that keeps around the lines we've already seen -export cdefs -if cdefs == nil - cdefs = Set! - -export imported -if imported == nil - imported = Set! - --- some things are just too complex for the LuaJIT C parser to digest. We --- usually don't need them anyway. -filter_complex_blocks = (body) -> - result = {} - for line in body\gmatch("[^\r\n]+") - -- remove all lines that contain Objective-C block syntax, the LuaJIT ffi - -- doesn't understand it. - if string.find(line, "(^)", 1, true) ~= nil - continue - if string.find(line, "_ISwupper", 1, true) ~= nil - continue - result[#result + 1] = line - table.concat(result, "\n") - --- use this helper to import C files, you can pass multiple paths at once, --- this helper will return the C namespace of the nvim library. --- cimport = (path) -> -cimport = (...) -> - -- filter out paths we've already imported - paths = [path for path in *{...} when not imported\contains(path)] - for path in *paths - imported\add(path) - - if #paths == 0 - return libnvim - - -- preprocess the header - stream = Preprocess.preprocess_stream(unpack(paths)) - body = stream\read("*a") - stream\close! - - -- format it (so that the lines are "unique" statements), also filter out - -- Objective-C blocks - body = formatc(body) - body = filter_complex_blocks(body) - - -- add the formatted lines to a set - new_cdefs = Set! - for line in body\gmatch("[^\r\n]+") - new_cdefs\add(trim(line)) - - -- subtract the lines we've already imported from the new lines, then add - -- the new unique lines to the old lines (so they won't be imported again) - new_cdefs\diff(cdefs) - cdefs\union(new_cdefs) - - if new_cdefs\size! == 0 - -- if there's no new lines, just return - return libnvim - - -- request a sorted version of the new lines (same relative order as the - -- original preprocessed file) and feed that to the LuaJIT ffi - new_lines = new_cdefs\to_table! - ffi.cdef(table.concat(new_lines, "\n")) - - return libnvim - -cppimport = (path) -> - return cimport Paths.test_include_path .. '/' .. path - -cimport './src/nvim/types.h' - --- take a pointer to a C-allocated string and return an interned --- version while also freeing the memory -internalize = (cdata, len) -> - ffi.gc cdata, ffi.C.free - return ffi.string cdata, len - -cstr = ffi.typeof 'char[?]' - -to_cstr = (string) -> - cstr (string.len string) + 1, string - -export vim_init_called --- initialize some global variables, this is still necessary to unit test --- functions that rely on global state. -vim_init = -> - if vim_init_called ~= nil - return - -- import os_unix.h for mch_early_init(), which initializes some globals - os = cimport './src/nvim/os_unix.h' - os.mch_early_init! - vim_init_called = true - --- C constants. -NULL = ffi.cast 'void*', 0 - -OK = 1 -FAIL = 0 - -return { - cimport: cimport - cppimport: cppimport - internalize: internalize - eq: (expected, actual) -> assert.are.same expected, actual - neq: (expected, actual) -> assert.are_not.same expected, actual - ffi: ffi - lib: libnvim - cstr: cstr - to_cstr: to_cstr - vim_init: vim_init - NULL: NULL - OK: OK - FAIL: FAIL -} From d39aa51e942c525d55b13490cb13806f8b72f4f1 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Sat, 30 Aug 2014 10:53:41 -0300 Subject: [PATCH 03/11] unittest: convert env_spec.moon to lua --- test/unit/os/env_spec.lua | 130 +++++++++++++++++++++++++++++++++++++ test/unit/os/env_spec.moon | 106 ------------------------------ 2 files changed, 130 insertions(+), 106 deletions(-) create mode 100644 test/unit/os/env_spec.lua delete mode 100644 test/unit/os/env_spec.moon diff --git a/test/unit/os/env_spec.lua b/test/unit/os/env_spec.lua new file mode 100644 index 00000000000000..d04754f5ee65ce --- /dev/null +++ b/test/unit/os/env_spec.lua @@ -0,0 +1,130 @@ +local helpers = require('test.unit.helpers') + +local cimport = helpers.cimport +local internalize = helpers.internalize +local eq = helpers.eq +local ffi = helpers.ffi +local lib = helpers.lib +local cstr = helpers.cstr +local to_cstr = helpers.to_cstr +local NULL = helpers.NULL + +require('lfs') + +local env = cimport('./src/nvim/os/os.h') + +describe('env function', function() + function os_setenv(name, value, override) + return env.os_setenv((to_cstr(name)), (to_cstr(value)), override) + end + + function os_getenv(name) + local rval = env.os_getenv((to_cstr(name))) + if rval ~= NULL then + return ffi.string(rval) + else + return NULL + end + end + + describe('os_setenv', function() + local OK = 0 + + it('sets an env variable and returns OK', function() + local name = 'NEOVIM_UNIT_TEST_SETENV_1N' + local value = 'NEOVIM_UNIT_TEST_SETENV_1V' + eq(nil, os.getenv(name)) + eq(OK, (os_setenv(name, value, 1))) + eq(value, os.getenv(name)) + end) + + it("dosn't overwrite an env variable if overwrite is 0", function() + local name = 'NEOVIM_UNIT_TEST_SETENV_2N' + local value = 'NEOVIM_UNIT_TEST_SETENV_2V' + local value_updated = 'NEOVIM_UNIT_TEST_SETENV_2V_UPDATED' + eq(OK, (os_setenv(name, value, 0))) + eq(value, os.getenv(name)) + eq(OK, (os_setenv(name, value_updated, 0))) + eq(value, os.getenv(name)) + end) + end) + + describe('os_getenv', function() + it('reads an env variable', function() + local name = 'NEOVIM_UNIT_TEST_GETENV_1N' + local value = 'NEOVIM_UNIT_TEST_GETENV_1V' + eq(NULL, os_getenv(name)) + -- need to use os_setenv, because lua dosn't have a setenv function + os_setenv(name, value, 1) + eq(value, os_getenv(name)) + end) + + it('returns NULL if the env variable is not found', function() + local name = 'NEOVIM_UNIT_TEST_GETENV_NOTFOUND' + return eq(NULL, os_getenv(name)) + end) + end) + + describe('os_getenvname_at_index', function() + it('returns names of environment variables', function() + local test_name = 'NEOVIM_UNIT_TEST_GETENVNAME_AT_INDEX_1N' + local test_value = 'NEOVIM_UNIT_TEST_GETENVNAME_AT_INDEX_1V' + os_setenv(test_name, test_value, 1) + local i = 0 + local names = { } + local found_name = false + local name = env.os_getenvname_at_index(i) + while name ~= NULL do + table.insert(names, ffi.string(name)) + if (ffi.string(name)) == test_name then + found_name = true + end + i = i + 1 + name = env.os_getenvname_at_index(i) + end + eq(true, (table.getn(names)) > 0) + eq(true, found_name) + end) + + it('returns NULL if the index is out of bounds', function() + local huge = ffi.new('size_t', 10000) + local maxuint32 = ffi.new('size_t', 4294967295) + eq(NULL, env.os_getenvname_at_index(huge)) + eq(NULL, env.os_getenvname_at_index(maxuint32)) + + if ffi.abi('64bit') then + -- couldn't use a bigger number because it gets converted to + -- double somewere, should be big enough anyway + -- maxuint64 = ffi.new 'size_t', 18446744073709551615 + local maxuint64 = ffi.new('size_t', 18446744073709000000) + eq(NULL, env.os_getenvname_at_index(maxuint64)) + end + end) + end) + + describe('os_get_pid', function() + it('returns the process ID', function() + local stat_file = io.open('/proc/self/stat') + if stat_file then + local stat_str = stat_file:read('*l') + stat_file:close() + local pid = tonumber((stat_str:match('%d+'))) + eq(pid, tonumber(env.os_get_pid())) + else + -- /proc is not available on all systems, test if pid is nonzero. + eq(true, (env.os_get_pid() > 0)) + end + end) + end) + + describe('os_get_hostname', function() + it('returns the hostname', function() + local handle = io.popen('hostname') + local hostname = handle:read('*l') + handle:close() + local hostname_buf = cstr(255, '') + env.os_get_hostname(hostname_buf, 255) + eq(hostname, (ffi.string(hostname_buf))) + end) + end) +end) diff --git a/test/unit/os/env_spec.moon b/test/unit/os/env_spec.moon deleted file mode 100644 index ab5b940a584c98..00000000000000 --- a/test/unit/os/env_spec.moon +++ /dev/null @@ -1,106 +0,0 @@ -{:cimport, :internalize, :eq, :ffi, :lib, :cstr, :to_cstr, :NULL} = require 'test.unit.helpers' -require 'lfs' - -env = cimport './src/nvim/os/os.h' - -describe 'env function', -> - - os_setenv = (name, value, override) -> - env.os_setenv (to_cstr name), (to_cstr value), override - - os_getenv = (name) -> - rval = env.os_getenv (to_cstr name) - if rval != NULL - ffi.string rval - else - NULL - - describe 'os_setenv', -> - - OK = 0 - - it 'sets an env variable and returns OK', -> - name = 'NEOVIM_UNIT_TEST_SETENV_1N' - value = 'NEOVIM_UNIT_TEST_SETENV_1V' - eq nil, os.getenv name - eq OK, (os_setenv name, value, 1) - eq value, os.getenv name - - it "dosn't overwrite an env variable if overwrite is 0", -> - name = 'NEOVIM_UNIT_TEST_SETENV_2N' - value = 'NEOVIM_UNIT_TEST_SETENV_2V' - value_updated = 'NEOVIM_UNIT_TEST_SETENV_2V_UPDATED' - eq OK, (os_setenv name, value, 0) - eq value, os.getenv name - eq OK, (os_setenv name, value_updated, 0) - eq value, os.getenv name - - describe 'os_getenv', -> - - it 'reads an env variable', -> - name = 'NEOVIM_UNIT_TEST_GETENV_1N' - value = 'NEOVIM_UNIT_TEST_GETENV_1V' - eq NULL, os_getenv name - -- need to use os_setenv, because lua dosn't have a setenv function - os_setenv name, value, 1 - eq value, os_getenv name - - it 'returns NULL if the env variable is not found', -> - name = 'NEOVIM_UNIT_TEST_GETENV_NOTFOUND' - eq NULL, os_getenv name - - describe 'os_getenvname_at_index', -> - - it 'returns names of environment variables', -> - test_name = 'NEOVIM_UNIT_TEST_GETENVNAME_AT_INDEX_1N' - test_value = 'NEOVIM_UNIT_TEST_GETENVNAME_AT_INDEX_1V' - os_setenv test_name, test_value, 1 - i = 0 - names = {} - found_name = false - name = env.os_getenvname_at_index i - while name != NULL - table.insert names, ffi.string name - if (ffi.string name) == test_name - found_name = true - i += 1 - name = env.os_getenvname_at_index i - - eq true, (table.getn names) > 0 - eq true, found_name - - it 'returns NULL if the index is out of bounds', -> - huge = ffi.new 'size_t', 10000 - maxuint32 = ffi.new 'size_t', 4294967295 - eq NULL, env.os_getenvname_at_index huge - eq NULL, env.os_getenvname_at_index maxuint32 - if ffi.abi '64bit' - -- couldn't use a bigger number because it gets converted to - -- double somewere, should be big enough anyway - -- maxuint64 = ffi.new 'size_t', 18446744073709551615 - maxuint64 = ffi.new 'size_t', 18446744073709000000 - eq NULL, env.os_getenvname_at_index maxuint64 - - describe 'os_get_pid', -> - - it 'returns the process ID', -> - stat_file = io.open '/proc/self/stat' - if stat_file - stat_str = stat_file\read '*l' - stat_file\close! - pid = tonumber (stat_str\match '%d+') - eq pid, tonumber env.os_get_pid! - else - -- /proc is not available on all systems, test if pid is nonzero. - eq true, (env.os_get_pid! > 0) - - describe 'os_get_hostname', -> - - it 'returns the hostname', -> - handle = io.popen 'hostname' - hostname = handle\read '*l' - handle\close! - hostname_buf = cstr 255, '' - env.os_get_hostname hostname_buf, 255 - eq hostname, (ffi.string hostname_buf) - From 227e38f0c66bdf146b1ce9dc3990aef9970565fd Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Sat, 30 Aug 2014 11:26:56 -0300 Subject: [PATCH 04/11] unittest: convert fs_spec.moon to lua --- test/unit/os/fs_spec.lua | 655 ++++++++++++++++++++++++++++++++++++++ test/unit/os/fs_spec.moon | 525 ------------------------------ 2 files changed, 655 insertions(+), 525 deletions(-) create mode 100644 test/unit/os/fs_spec.lua delete mode 100644 test/unit/os/fs_spec.moon diff --git a/test/unit/os/fs_spec.lua b/test/unit/os/fs_spec.lua new file mode 100644 index 00000000000000..92e968abd72862 --- /dev/null +++ b/test/unit/os/fs_spec.lua @@ -0,0 +1,655 @@ +local helpers = require('test.unit.helpers') + +local cimport = helpers.cimport +local cppimport = helpers.cppimport +local internalize = helpers.internalize +local eq = helpers.eq +local neq = helpers.neq +local ffi = helpers.ffi +local lib = helpers.lib +local cstr = helpers.cstr +local to_cstr = helpers.to_cstr +local OK = helpers.OK +local FAIL = helpers.FAIL + +require('lfs') +require('bit') + +local fs = cimport('./src/nvim/os/os.h') +cppimport('sys/stat.h') +cppimport('sys/fcntl.h') +cppimport('sys/errno.h') + +function assert_file_exists(filepath) + eq(false, nil == (lfs.attributes(filepath, 'r'))) +end + +function assert_file_does_not_exist(filepath) + eq(true, nil == (lfs.attributes(filepath, 'r'))) +end + +describe('fs function', function() + + setup(function() + lfs.mkdir('unit-test-directory'); + io.open('unit-test-directory/test.file', 'w').close() + io.open('unit-test-directory/test_2.file', 'w').close() + lfs.link('test.file', 'unit-test-directory/test_link.file', true) + -- Since the tests are executed, they are called by an executable. We use + -- that executable for several asserts. + absolute_executable = arg[0] + -- Split absolute_executable into a directory and the actual file name for + -- later usage. + directory, executable_name = string.match(absolute_executable, '^(.*)/(.*)$') + end) + + teardown(function() + os.remove('unit-test-directory/test.file') + os.remove('unit-test-directory/test_2.file') + os.remove('unit-test-directory/test_link.file') + lfs.rmdir('unit-test-directory') + end) + + describe('os_dirname', function() + function os_dirname(buf, len) + return fs.os_dirname(buf, len) + end + + before_each(function() + len = (string.len(lfs.currentdir())) + 1 + buf = cstr(len, '') + end) + + it('returns OK and writes current directory into the buffer if it is large\n enough', function() + eq(OK, (os_dirname(buf, len))) + eq(lfs.currentdir(), (ffi.string(buf))) + end) + + -- What kind of other failing cases are possible? + it('returns FAIL if the buffer is too small', function() + local buf = cstr((len - 1), '') + eq(FAIL, (os_dirname(buf, (len - 1)))) + end) + end) + + function os_isdir(name) + return fs.os_isdir((to_cstr(name))) + end + + describe('os_isdir', function() + it('returns false if an empty string is given', function() + eq(false, (os_isdir(''))) + end) + + it('returns false if a nonexisting directory is given', function() + eq(false, (os_isdir('non-existing-directory'))) + end) + + it('returns false if a nonexisting absolute directory is given', function() + eq(false, (os_isdir('/non-existing-directory'))) + end) + + it('returns false if an existing file is given', function() + eq(false, (os_isdir('unit-test-directory/test.file'))) + end) + + it('returns true if the current directory is given', function() + eq(true, (os_isdir('.'))) + end) + + it('returns true if the parent directory is given', function() + eq(true, (os_isdir('..'))) + end) + + it('returns true if an arbitrary directory is given', function() + eq(true, (os_isdir('unit-test-directory'))) + end) + + it('returns true if an absolute directory is given', function() + eq(true, (os_isdir(directory))) + end) + end) + + describe('os_can_exe', function() + function os_can_exe(name) + return fs.os_can_exe((to_cstr(name))) + end + + it('returns false when given a directory', function() + eq(false, (os_can_exe('./unit-test-directory'))) + end) + + it('returns false when given a regular file without executable bit set', function() + eq(false, (os_can_exe('unit-test-directory/test.file'))) + end) + + it('returns false when the given file does not exists', function() + eq(false, (os_can_exe('does-not-exist.file'))) + end) + + it('returns true when given an executable inside $PATH', function() + eq(true, (os_can_exe(executable_name))) + end) + + it('returns true when given an executable relative to the current dir', function() + local old_dir = lfs.currentdir() + lfs.chdir(directory) + local relative_executable = './' .. executable_name + eq(true, (os_can_exe(relative_executable))) + lfs.chdir(old_dir) + end) + end) + + describe('file permissions', function() + function os_getperm(filename) + local perm = fs.os_getperm((to_cstr(filename))) + return tonumber(perm) + end + + function os_setperm(filename, perm) + return fs.os_setperm((to_cstr(filename)), perm) + end + + function os_fchown(filename, user_id, group_id) + local fd = ffi.C.open(filename, 0) + local res = fs.os_fchown(fd, user_id, group_id) + ffi.C.close(fd) + return res + end + + function os_file_is_readonly(filename) + return fs.os_file_is_readonly((to_cstr(filename))) + end + + function os_file_is_writable(filename) + return fs.os_file_is_writable((to_cstr(filename))) + end + + function bit_set(number, check_bit) + return 0 ~= (bit.band(number, check_bit)) + end + + function set_bit(number, to_set) + return bit.bor(number, to_set) + end + + function unset_bit(number, to_unset) + return bit.band(number, (bit.bnot(to_unset))) + end + + describe('os_getperm', function() + it('returns -1 when the given file does not exist', function() + eq(-1, (os_getperm('non-existing-file'))) + end) + + it('returns a perm > 0 when given an existing file', function() + assert.is_true((os_getperm('unit-test-directory')) > 0) + end) + + it('returns S_IRUSR when the file is readable', function() + local perm = os_getperm('unit-test-directory') + assert.is_true((bit_set(perm, ffi.C.kS_IRUSR))) + end) + end) + + describe('os_setperm', function() + it('can set and unset the executable bit of a file', function() + local perm = os_getperm('unit-test-directory/test.file') + perm = unset_bit(perm, ffi.C.kS_IXUSR) + eq(OK, (os_setperm('unit-test-directory/test.file', perm))) + perm = os_getperm('unit-test-directory/test.file') + assert.is_false((bit_set(perm, ffi.C.kS_IXUSR))) + perm = set_bit(perm, ffi.C.kS_IXUSR) + eq(OK, os_setperm('unit-test-directory/test.file', perm)) + perm = os_getperm('unit-test-directory/test.file') + assert.is_true((bit_set(perm, ffi.C.kS_IXUSR))) + end) + + it('fails if given file does not exist', function() + local perm = ffi.C.kS_IXUSR + eq(FAIL, (os_setperm('non-existing-file', perm))) + end) + end) + + describe('os_fchown', function() + local filename = 'unit-test-directory/test.file' + it('does not change owner and group if respective IDs are equal to -1', function() + local uid = lfs.attributes(filename, 'uid') + local gid = lfs.attributes(filename, 'gid') + eq(0, os_fchown(filename, -1, -1)) + eq(uid, lfs.attributes(filename, 'uid')) + return eq(gid, lfs.attributes(filename, 'gid')) + end) + + it('owner of a file may change the group of the file to any group of which that owner is a member', function() + -- Some systems may not have `id` utility. + if (os.execute('id -G &> /dev/null') == 0) then + local file_gid = lfs.attributes(filename, 'gid') + + -- Gets ID of any group of which current user is a member except the + -- group that owns the file. + local id_fd = io.popen('id -G') + local new_gid = id_fd:read('*n') + if (new_gid == file_gid) then + new_gid = id_fd:read('*n') + end + id_fd:close() + + -- User can be a member of only one group. + -- In that case we can not perform this test. + if new_gid then + eq(0, (os_fchown(filename, -1, new_gid))) + eq(new_gid, (lfs.attributes(filename, 'gid'))) + end + end + end) + + it('returns nonzero if process has not enough permissions', function() + -- On Windows `os_fchown` always returns 0 + -- because `uv_fs_chown` is no-op on this platform. + if (ffi.os ~= 'Windows' and ffi.C.geteuid() ~= 0) then + -- chown to root + neq(0, os_fchown(filename, 0, 0)) + end + end) + end) + + describe('os_file_is_readonly', function() + it('returns true if the file is readonly', function() + local perm = os_getperm('unit-test-directory/test.file') + local perm_orig = perm + perm = unset_bit(perm, ffi.C.kS_IWUSR) + perm = unset_bit(perm, ffi.C.kS_IWGRP) + perm = unset_bit(perm, ffi.C.kS_IWOTH) + eq(OK, (os_setperm('unit-test-directory/test.file', perm))) + eq(true, os_file_is_readonly('unit-test-directory/test.file')) + eq(OK, (os_setperm('unit-test-directory/test.file', perm_orig))) + end) + + it('returns false if the file is writable', function() + eq(false, os_file_is_readonly('unit-test-directory/test.file')) + end) + end) + + describe('os_file_is_writable', function() + it('returns 0 if the file is readonly', function() + local perm = os_getperm('unit-test-directory/test.file') + local perm_orig = perm + perm = unset_bit(perm, ffi.C.kS_IWUSR) + perm = unset_bit(perm, ffi.C.kS_IWGRP) + perm = unset_bit(perm, ffi.C.kS_IWOTH) + eq(OK, (os_setperm('unit-test-directory/test.file', perm))) + eq(0, os_file_is_writable('unit-test-directory/test.file')) + eq(OK, (os_setperm('unit-test-directory/test.file', perm_orig))) + end) + + it('returns 1 if the file is writable', function() + eq(1, os_file_is_writable('unit-test-directory/test.file')) + end) + + it('returns 2 when given a folder with rights to write into', function() + eq(2, os_file_is_writable('unit-test-directory')) + end) + end) + end) + + describe('file operations', function() + function os_file_exists(filename) + return fs.os_file_exists((to_cstr(filename))) + end + + function os_rename(path, new_path) + return fs.os_rename((to_cstr(path)), (to_cstr(new_path))) + end + + function os_remove(path) + return fs.os_remove((to_cstr(path))) + end + + function os_open(path, flags, mode) + return fs.os_open((to_cstr(path)), flags, mode) + end + + describe('os_file_exists', function() + it('returns false when given a non-existing file', function() + eq(false, (os_file_exists('non-existing-file'))) + end) + + it('returns true when given an existing file', function() + eq(true, (os_file_exists('unit-test-directory/test.file'))) + end) + end) + + describe('os_rename', function() + local test = 'unit-test-directory/test.file' + local not_exist = 'unit-test-directory/not_exist.file' + + it('can rename file if destination file does not exist', function() + eq(OK, (os_rename(test, not_exist))) + eq(false, (os_file_exists(test))) + eq(true, (os_file_exists(not_exist))) + eq(OK, (os_rename(not_exist, test))) -- restore test file + end) + + it('fail if source file does not exist', function() + eq(FAIL, (os_rename(not_exist, test))) + end) + + it('can overwrite destination file if it exists', function() + local other = 'unit-test-directory/other.file' + local file = io.open(other, 'w') + file:write('other') + file:flush() + file:close() + + eq(OK, (os_rename(other, test))) + eq(false, (os_file_exists(other))) + eq(true, (os_file_exists(test))) + file = io.open(test, 'r') + eq('other', (file:read('*all'))) + file:close() + end) + end) + + describe('os_remove', function() + before_each(function() + io.open('unit-test-directory/test_remove.file', 'w').close() + end) + + after_each(function() + os.remove('unit-test-directory/test_remove.file') + end) + + it('returns non-zero when given a non-existing file', function() + neq(0, (os_remove('non-existing-file'))) + end) + + it('removes the given file and returns 0', function() + local f = 'unit-test-directory/test_remove.file' + assert_file_exists(f) + eq(0, (os_remove(f))) + assert_file_does_not_exist(f) + end) + end) + + describe('os_open', function() + before_each(function() + io.open('unit-test-directory/test_existing.file', 'w').close() + end) + + after_each(function() + os.remove('unit-test-directory/test_existing.file') + os.remove('test_new_file') + end) + + local new_file = 'test_new_file' + local existing_file = 'unit-test-directory/test_existing.file' + + it('returns -ENOENT for O_RDWR on a non-existing file', function() + eq(-ffi.C.kENOENT, (os_open('non-existing-file', ffi.C.kO_RDWR, 0))) + end) + + it('returns non-negative for O_CREAT on a non-existing file', function() + assert_file_does_not_exist(new_file) + assert.is_true(0 <= (os_open(new_file, ffi.C.kO_CREAT, 0))) + end) + + it('returns non-negative for O_CREAT on a existing file', function() + assert_file_exists(existing_file) + assert.is_true(0 <= (os_open(existing_file, ffi.C.kO_CREAT, 0))) + end) + + it('returns -EEXIST for O_CREAT|O_EXCL on a existing file', function() + assert_file_exists(existing_file) + eq(-ffi.C.kEEXIST, (os_open(existing_file, (bit.bor(ffi.C.kO_CREAT, ffi.C.kO_EXCL)), 0))) + end) + + it('sets `rwx` permissions for O_CREAT 700', function() + assert_file_does_not_exist(new_file) + --create the file + os_open(new_file, ffi.C.kO_CREAT, tonumber("700", 8)) + --verify permissions + eq('rwx------', lfs.attributes(new_file)['permissions']) + end) + + it('sets `rw` permissions for O_CREAT 600', function() + assert_file_does_not_exist(new_file) + --create the file + os_open(new_file, ffi.C.kO_CREAT, tonumber("600", 8)) + --verify permissions + eq('rw-------', lfs.attributes(new_file)['permissions']) + end) + + it('returns a non-negative file descriptor for an existing file', function() + assert.is_true(0 <= (os_open(existing_file, ffi.C.kO_RDWR, 0))) + end) + end) + end) + + describe('folder operations', function() + function os_mkdir(path, mode) + return fs.os_mkdir(to_cstr(path), mode) + end + + function os_rmdir(path) + return fs.os_rmdir(to_cstr(path)) + end + + describe('os_mkdir', function() + it('returns non-zero when given an already existing directory', function() + local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR + neq(0, (os_mkdir('unit-test-directory', mode))) + end) + + it('creates a directory and returns 0', function() + local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR + eq(false, (os_isdir('unit-test-directory/new-dir'))) + eq(0, (os_mkdir('unit-test-directory/new-dir', mode))) + eq(true, (os_isdir('unit-test-directory/new-dir'))) + lfs.rmdir('unit-test-directory/new-dir') + end) + end) + + describe('os_rmdir', function() + it('returns non_zero when given a non-existing directory', function() + neq(0, (os_rmdir('non-existing-directory'))) + end) + + it('removes the given directory and returns 0', function() + lfs.mkdir('unit-test-directory/new-dir') + eq(0, (os_rmdir('unit-test-directory/new-dir', mode))) + eq(false, (os_isdir('unit-test-directory/new-dir'))) + end) + end) + + describe('FileInfo', function() + function file_info_new() + local file_info = ffi.new('FileInfo[1]') + file_info[0].stat.st_ino = 0 + file_info[0].stat.st_dev = 0 + return file_info + end + + function is_file_info_filled(file_info) + return file_info[0].stat.st_ino > 0 and file_info[0].stat.st_dev > 0 + end + + function file_id_new() + local file_info = ffi.new('FileID[1]') + file_info[0].inode = 0 + file_info[0].device_id = 0 + return file_info + end + + describe('os_get_file_info', function() + it('returns false if given a non-existing file', function() + local file_info = file_info_new() + assert.is_false((fs.os_get_file_info('/non-existent', file_info))) + end) + + it('returns true if given an existing file and fills file_info', function() + local file_info = file_info_new() + local path = 'unit-test-directory/test.file' + assert.is_true((fs.os_get_file_info(path, file_info))) + assert.is_true((is_file_info_filled(file_info))) + end) + + it('returns the file info of the linked file, not the link', function() + local file_info = file_info_new() + local path = 'unit-test-directory/test_link.file' + assert.is_true((fs.os_get_file_info(path, file_info))) + assert.is_true((is_file_info_filled(file_info))) + local mode = tonumber(file_info[0].stat.st_mode) + return eq(ffi.C.kS_IFREG, (bit.band(mode, ffi.C.kS_IFMT))) + end) + end) + + describe('os_get_file_info_link', function() + it('returns false if given a non-existing file', function() + local file_info = file_info_new() + assert.is_false((fs.os_get_file_info_link('/non-existent', file_info))) + end) + + it('returns true if given an existing file and fills file_info', function() + local file_info = file_info_new() + local path = 'unit-test-directory/test.file' + assert.is_true((fs.os_get_file_info_link(path, file_info))) + assert.is_true((is_file_info_filled(file_info))) + end) + + it('returns the file info of the link, not the linked file', function() + local file_info = file_info_new() + local path = 'unit-test-directory/test_link.file' + assert.is_true((fs.os_get_file_info_link(path, file_info))) + assert.is_true((is_file_info_filled(file_info))) + local mode = tonumber(file_info[0].stat.st_mode) + eq(ffi.C.kS_IFLNK, (bit.band(mode, ffi.C.kS_IFMT))) + end) + end) + + describe('os_get_file_info_fd', function() + it('returns false if given an invalid file descriptor', function() + local file_info = file_info_new() + assert.is_false((fs.os_get_file_info_fd(-1, file_info))) + end) + + it('returns true if given a file descriptor and fills file_info', function() + local file_info = file_info_new() + local path = 'unit-test-directory/test.file' + local fd = ffi.C.open(path, 0) + assert.is_true((fs.os_get_file_info_fd(fd, file_info))) + assert.is_true((is_file_info_filled(file_info))) + ffi.C.close(fd) + end) + end) + + describe('os_file_info_id_equal', function() + it('returns false if file infos represent different files', function() + local file_info_1 = file_info_new() + local file_info_2 = file_info_new() + local path_1 = 'unit-test-directory/test.file' + local path_2 = 'unit-test-directory/test_2.file' + assert.is_true((fs.os_get_file_info(path_1, file_info_1))) + assert.is_true((fs.os_get_file_info(path_2, file_info_2))) + assert.is_false((fs.os_file_info_id_equal(file_info_1, file_info_2))) + end) + + it('returns true if file infos represent the same file', function() + local file_info_1 = file_info_new() + local file_info_2 = file_info_new() + local path = 'unit-test-directory/test.file' + assert.is_true((fs.os_get_file_info(path, file_info_1))) + assert.is_true((fs.os_get_file_info(path, file_info_2))) + assert.is_true((fs.os_file_info_id_equal(file_info_1, file_info_2))) + end) + + it('returns true if file infos represent the same file (symlink)', function() + local file_info_1 = file_info_new() + local file_info_2 = file_info_new() + local path_1 = 'unit-test-directory/test.file' + local path_2 = 'unit-test-directory/test_link.file' + assert.is_true((fs.os_get_file_info(path_1, file_info_1))) + assert.is_true((fs.os_get_file_info(path_2, file_info_2))) + assert.is_true((fs.os_file_info_id_equal(file_info_1, file_info_2))) + end) + end) + + describe('os_file_info_get_id', function() + it('extracts ino/dev from file_info into file_id', function() + local file_info = file_info_new() + local file_id = file_id_new() + local path = 'unit-test-directory/test.file' + assert.is_true((fs.os_get_file_info(path, file_info))) + fs.os_file_info_get_id(file_info, file_id) + eq(file_info[0].stat.st_ino, file_id[0].inode) + eq(file_info[0].stat.st_dev, file_id[0].device_id) + end) + end) + + describe('os_file_info_get_inode', function() + it('returns the inode from file_info', function() + local file_info = file_info_new() + local path = 'unit-test-directory/test.file' + assert.is_true((fs.os_get_file_info(path, file_info))) + local inode = fs.os_file_info_get_inode(file_info) + eq(file_info[0].stat.st_ino, inode) + end) + end) + + describe('os_get_file_id', function() + it('returns false if given an non-existing file', function() + local file_id = file_id_new() + assert.is_false((fs.os_get_file_id('/non-existent', file_id))) + end) + + it('returns true if given an existing file and fills file_id', function() + local file_id = file_id_new() + local path = 'unit-test-directory/test.file' + assert.is_true((fs.os_get_file_id(path, file_id))) + assert.is_true(0 < file_id[0].inode) + assert.is_true(0 < file_id[0].device_id) + end) + end) + + describe('os_file_id_equal', function() + it('returns true if two FileIDs are equal', function() + local file_id = file_id_new() + local path = 'unit-test-directory/test.file' + assert.is_true((fs.os_get_file_id(path, file_id))) + assert.is_true((fs.os_file_id_equal(file_id, file_id))) + end) + + it('returns false if two FileIDs are not equal', function() + local file_id_1 = file_id_new() + local file_id_2 = file_id_new() + local path_1 = 'unit-test-directory/test.file' + local path_2 = 'unit-test-directory/test_2.file' + assert.is_true((fs.os_get_file_id(path_1, file_id_1))) + assert.is_true((fs.os_get_file_id(path_2, file_id_2))) + assert.is_false((fs.os_file_id_equal(file_id_1, file_id_2))) + end) + end) + + describe('os_file_id_equal_file_info', function() + it('returns true if file_id and file_info represent the same file', function() + local file_id = file_id_new() + local file_info = file_info_new() + local path = 'unit-test-directory/test.file' + assert.is_true((fs.os_get_file_id(path, file_id))) + assert.is_true((fs.os_get_file_info(path, file_info))) + assert.is_true((fs.os_file_id_equal_file_info(file_id, file_info))) + end) + + it('returns false if file_id and file_info represent different files', function() + local file_id = file_id_new() + local file_info = file_info_new() + local path_1 = 'unit-test-directory/test.file' + local path_2 = 'unit-test-directory/test_2.file' + assert.is_true((fs.os_get_file_id(path_1, file_id))) + assert.is_true((fs.os_get_file_info(path_2, file_info))) + assert.is_false((fs.os_file_id_equal_file_info(file_id, file_info))) + end) + end) + end) + end) +end) diff --git a/test/unit/os/fs_spec.moon b/test/unit/os/fs_spec.moon deleted file mode 100644 index dd787e76cdf97d..00000000000000 --- a/test/unit/os/fs_spec.moon +++ /dev/null @@ -1,525 +0,0 @@ -{:cimport, :cppimport, :internalize, :eq, :neq, :ffi, :lib, :cstr, :to_cstr, :OK, :FAIL} = require 'test.unit.helpers' -require 'lfs' -require 'bit' - -fs = cimport './src/nvim/os/os.h' - -cppimport 'sys/stat.h' -cppimport 'sys/fcntl.h' -cppimport 'sys/errno.h' - -assert_file_exists = (filepath) -> - eq false, nil == (lfs.attributes filepath, 'r') - -assert_file_does_not_exist = (filepath) -> - eq true, nil == (lfs.attributes filepath, 'r') - -describe 'fs function', -> - setup -> - lfs.mkdir 'unit-test-directory' - (io.open 'unit-test-directory/test.file', 'w').close! - (io.open 'unit-test-directory/test_2.file', 'w').close! - lfs.link 'test.file', 'unit-test-directory/test_link.file', true - - -- Since the tests are executed, they are called by an executable. We use - -- that executable for several asserts. - export absolute_executable = arg[0] - - -- Split absolute_executable into a directory and the actual file name for - -- later usage. - export directory, executable_name = string.match(absolute_executable, '^(.*)/(.*)$') - - teardown -> - os.remove 'unit-test-directory/test.file' - os.remove 'unit-test-directory/test_2.file' - os.remove 'unit-test-directory/test_link.file' - lfs.rmdir 'unit-test-directory' - - describe 'os_dirname', -> - os_dirname = (buf, len) -> - fs.os_dirname buf, len - - before_each -> - export len = (string.len lfs.currentdir!) + 1 - export buf = cstr len, '' - - it 'returns OK and writes current directory into the buffer if it is large - enough', -> - eq OK, (os_dirname buf, len) - eq lfs.currentdir!, (ffi.string buf) - - -- What kind of other failing cases are possible? - it 'returns FAIL if the buffer is too small', -> - buf = cstr (len-1), '' - eq FAIL, (os_dirname buf, (len-1)) - - os_isdir = (name) -> - fs.os_isdir (to_cstr name) - - describe 'os_isdir', -> - it 'returns false if an empty string is given', -> - eq false, (os_isdir '') - - it 'returns false if a nonexisting directory is given', -> - eq false, (os_isdir 'non-existing-directory') - - it 'returns false if a nonexisting absolute directory is given', -> - eq false, (os_isdir '/non-existing-directory') - - it 'returns false if an existing file is given', -> - eq false, (os_isdir 'unit-test-directory/test.file') - - it 'returns true if the current directory is given', -> - eq true, (os_isdir '.') - - it 'returns true if the parent directory is given', -> - eq true, (os_isdir '..') - - it 'returns true if an arbitrary directory is given', -> - eq true, (os_isdir 'unit-test-directory') - - it 'returns true if an absolute directory is given', -> - eq true, (os_isdir directory) - - describe 'os_can_exe', -> - os_can_exe = (name) -> - fs.os_can_exe (to_cstr name) - - it 'returns false when given a directory', -> - eq false, (os_can_exe './unit-test-directory') - - it 'returns false when given a regular file without executable bit set', -> - eq false, (os_can_exe 'unit-test-directory/test.file') - - it 'returns false when the given file does not exists', -> - eq false, (os_can_exe 'does-not-exist.file') - - it 'returns true when given an executable inside $PATH', -> - eq true, (os_can_exe executable_name) - - it 'returns true when given an executable relative to the current dir', -> - old_dir = lfs.currentdir! - lfs.chdir directory - relative_executable = './' .. executable_name - eq true, (os_can_exe relative_executable) - lfs.chdir old_dir - - describe 'file permissions', -> - os_getperm = (filename) -> - perm = fs.os_getperm (to_cstr filename) - tonumber perm - - os_setperm = (filename, perm) -> - fs.os_setperm (to_cstr filename), perm - - os_fchown = (filename, user_id, group_id) -> - fd = ffi.C.open filename, 0 - res = fs.os_fchown fd, user_id, group_id - ffi.C.close fd - return res - - os_file_is_readonly = (filename) -> - fs.os_file_is_readonly (to_cstr filename) - - os_file_is_writable = (filename) -> - fs.os_file_is_writable (to_cstr filename) - - bit_set = (number, check_bit) -> - if 0 == (bit.band number, check_bit) then false else true - - set_bit = (number, to_set) -> - return bit.bor number, to_set - - unset_bit = (number, to_unset) -> - return bit.band number, (bit.bnot to_unset) - - describe 'os_getperm', -> - it 'returns -1 when the given file does not exist', -> - eq -1, (os_getperm 'non-existing-file') - - it 'returns a perm > 0 when given an existing file', -> - assert.is_true (os_getperm 'unit-test-directory') > 0 - - it 'returns S_IRUSR when the file is readable', -> - perm = os_getperm 'unit-test-directory' - assert.is_true (bit_set perm, ffi.C.kS_IRUSR) - - describe 'os_setperm', -> - it 'can set and unset the executable bit of a file', -> - perm = os_getperm 'unit-test-directory/test.file' - - perm = unset_bit perm, ffi.C.kS_IXUSR - eq OK, (os_setperm 'unit-test-directory/test.file', perm) - - perm = os_getperm 'unit-test-directory/test.file' - assert.is_false (bit_set perm, ffi.C.kS_IXUSR) - - perm = set_bit perm, ffi.C.kS_IXUSR - eq OK, os_setperm 'unit-test-directory/test.file', perm - - perm = os_getperm 'unit-test-directory/test.file' - assert.is_true (bit_set perm, ffi.C.kS_IXUSR) - - it 'fails if given file does not exist', -> - perm = ffi.C.kS_IXUSR - eq FAIL, (os_setperm 'non-existing-file', perm) - - describe 'os_fchown', -> - filename = 'unit-test-directory/test.file' - - it 'does not change owner and group if respective IDs are equal to -1', -> - uid = lfs.attributes filename, 'uid' - gid = lfs.attributes filename, 'gid' - eq 0, os_fchown filename, -1, -1 - eq uid, lfs.attributes filename, 'uid' - eq gid, lfs.attributes filename, 'gid' - - it 'owner of a file may change the group of the file - to any group of which that owner is a member', -> - -- Some systems may not have `id` utility. - if (os.execute('id -G &> /dev/null') == 0) - file_gid = lfs.attributes filename, 'gid' - - -- Gets ID of any group of which current user is a member except the - -- group that owns the file. - id_fd = io.popen('id -G') - new_gid = id_fd\read '*n' - if (new_gid == file_gid) - new_gid = id_fd\read '*n' - id_fd\close! - - -- User can be a member of only one group. - -- In that case we can not perform this test. - if new_gid - eq 0, (os_fchown filename, -1, new_gid) - eq new_gid, (lfs.attributes filename, 'gid') - - it 'returns nonzero if process has not enough permissions', -> - -- On Windows `os_fchown` always returns 0 - -- because `uv_fs_chown` is no-op on this platform. - if (ffi.os != 'Windows' and ffi.C.geteuid! != 0) - -- chown to root - neq 0, os_fchown filename, 0, 0 - - describe 'os_file_is_readonly', -> - it 'returns true if the file is readonly', -> - perm = os_getperm 'unit-test-directory/test.file' - perm_orig = perm - perm = unset_bit perm, ffi.C.kS_IWUSR - perm = unset_bit perm, ffi.C.kS_IWGRP - perm = unset_bit perm, ffi.C.kS_IWOTH - eq OK, (os_setperm 'unit-test-directory/test.file', perm) - eq true, os_file_is_readonly 'unit-test-directory/test.file' - eq OK, (os_setperm 'unit-test-directory/test.file', perm_orig) - - it 'returns false if the file is writable', -> - eq false, os_file_is_readonly 'unit-test-directory/test.file' - - describe 'os_file_is_writable', -> - it 'returns 0 if the file is readonly', -> - perm = os_getperm 'unit-test-directory/test.file' - perm_orig = perm - perm = unset_bit perm, ffi.C.kS_IWUSR - perm = unset_bit perm, ffi.C.kS_IWGRP - perm = unset_bit perm, ffi.C.kS_IWOTH - eq OK, (os_setperm 'unit-test-directory/test.file', perm) - eq 0, os_file_is_writable 'unit-test-directory/test.file' - eq OK, (os_setperm 'unit-test-directory/test.file', perm_orig) - - it 'returns 1 if the file is writable', -> - eq 1, os_file_is_writable 'unit-test-directory/test.file' - - it 'returns 2 when given a folder with rights to write into', -> - eq 2, os_file_is_writable 'unit-test-directory' - - describe 'file operations', -> - os_file_exists = (filename) -> - fs.os_file_exists (to_cstr filename) - - os_rename = (path, new_path) -> - fs.os_rename (to_cstr path), (to_cstr new_path) - - os_remove = (path) -> - fs.os_remove (to_cstr path) - - os_open = (path, flags, mode) -> - fs.os_open (to_cstr path), flags, mode - - describe 'os_file_exists', -> - it 'returns false when given a non-existing file', -> - eq false, (os_file_exists 'non-existing-file') - - it 'returns true when given an existing file', -> - eq true, (os_file_exists 'unit-test-directory/test.file') - - describe 'os_rename', -> - test = 'unit-test-directory/test.file' - not_exist = 'unit-test-directory/not_exist.file' - - it 'can rename file if destination file does not exist', -> - eq OK, (os_rename test, not_exist) - eq false, (os_file_exists test) - eq true, (os_file_exists not_exist) - eq OK, (os_rename not_exist, test) -- restore test file - - it 'fail if source file does not exist', -> - eq FAIL, (os_rename not_exist, test) - - it 'can overwrite destination file if it exists', -> - other = 'unit-test-directory/other.file' - file = io.open other, 'w' - file\write 'other' - file\flush! - file\close! - - eq OK, (os_rename other, test) - eq false, (os_file_exists other) - eq true, (os_file_exists test) - file = io.open test, 'r' - eq 'other', (file\read '*all') - file\close! - - describe 'os_remove', -> - before_each -> - (io.open 'unit-test-directory/test_remove.file', 'w').close! - after_each -> - os.remove 'unit-test-directory/test_remove.file' - - it 'returns non-zero when given a non-existing file', -> - neq 0, (os_remove 'non-existing-file') - - it 'removes the given file and returns 0', -> - f = 'unit-test-directory/test_remove.file' - assert_file_exists f - eq 0, (os_remove f) - assert_file_does_not_exist f - - describe 'os_open', -> - before_each -> - (io.open 'unit-test-directory/test_existing.file', 'w').close! - after_each -> - os.remove 'unit-test-directory/test_existing.file' - os.remove 'test_new_file' - - new_file = 'test_new_file' - existing_file = 'unit-test-directory/test_existing.file' - - it 'returns -ENOENT for O_RDWR on a non-existing file', -> - eq -ffi.C.kENOENT, (os_open 'non-existing-file', ffi.C.kO_RDWR, 0) - - it 'returns non-negative for O_CREAT on a non-existing file', -> - assert_file_does_not_exist new_file - assert.is_true 0 <= (os_open new_file, ffi.C.kO_CREAT, 0) - - it 'returns non-negative for O_CREAT on a existing file', -> - assert_file_exists existing_file - assert.is_true 0 <= (os_open existing_file, ffi.C.kO_CREAT, 0) - - it 'returns -EEXIST for O_CREAT|O_EXCL on a existing file', -> - assert_file_exists existing_file - eq -ffi.C.kEEXIST, (os_open existing_file, (bit.bor ffi.C.kO_CREAT, ffi.C.kO_EXCL), 0) - - it 'sets `rwx` permissions for O_CREAT 700', -> - assert_file_does_not_exist new_file - --create the file - os_open new_file, ffi.C.kO_CREAT, tonumber("700", 8) - --verify permissions - eq 'rwx------', lfs.attributes(new_file)['permissions'] - - it 'sets `rw` permissions for O_CREAT 600', -> - assert_file_does_not_exist new_file - --create the file - os_open new_file, ffi.C.kO_CREAT, tonumber("600", 8) - --verify permissions - eq 'rw-------', lfs.attributes(new_file)['permissions'] - - it 'returns a non-negative file descriptor for an existing file', -> - assert.is_true 0 <= (os_open existing_file, ffi.C.kO_RDWR, 0) - - describe 'folder operations', -> - os_mkdir = (path, mode) -> - fs.os_mkdir (to_cstr path), mode - - os_rmdir = (path) -> - fs.os_rmdir (to_cstr path) - - describe 'os_mkdir', -> - it 'returns non-zero when given an already existing directory', -> - mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR - neq 0, (os_mkdir 'unit-test-directory', mode) - - it 'creates a directory and returns 0', -> - mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR - eq false, (os_isdir 'unit-test-directory/new-dir') - eq 0, (os_mkdir 'unit-test-directory/new-dir', mode) - eq true, (os_isdir 'unit-test-directory/new-dir') - lfs.rmdir 'unit-test-directory/new-dir' - - describe 'os_rmdir', -> - it 'returns non_zero when given a non-existing directory', -> - neq 0, (os_rmdir 'non-existing-directory') - - it 'removes the given directory and returns 0', -> - lfs.mkdir 'unit-test-directory/new-dir' - eq 0, (os_rmdir 'unit-test-directory/new-dir', mode) - eq false, (os_isdir 'unit-test-directory/new-dir') - - describe 'FileInfo', -> - - file_info_new = () -> - file_info = ffi.new 'FileInfo[1]' - file_info[0].stat.st_ino = 0 - file_info[0].stat.st_dev = 0 - file_info - - is_file_info_filled = (file_info) -> - file_info[0].stat.st_ino > 0 and file_info[0].stat.st_dev > 0 - - file_id_new = () -> - file_info = ffi.new 'FileID[1]' - file_info[0].inode = 0 - file_info[0].device_id = 0 - file_info - - describe 'os_get_file_info', -> - it 'returns false if given a non-existing file', -> - file_info = file_info_new! - assert.is_false (fs.os_get_file_info '/non-existent', file_info) - - it 'returns true if given an existing file and fills file_info', -> - file_info = file_info_new! - path = 'unit-test-directory/test.file' - assert.is_true (fs.os_get_file_info path, file_info) - assert.is_true (is_file_info_filled file_info) - - it 'returns the file info of the linked file, not the link', -> - file_info = file_info_new! - path = 'unit-test-directory/test_link.file' - assert.is_true (fs.os_get_file_info path, file_info) - assert.is_true (is_file_info_filled file_info) - mode = tonumber file_info[0].stat.st_mode - eq ffi.C.kS_IFREG, (bit.band mode, ffi.C.kS_IFMT) - - describe 'os_get_file_info_link', -> - it 'returns false if given a non-existing file', -> - file_info = file_info_new! - assert.is_false (fs.os_get_file_info_link '/non-existent', file_info) - - it 'returns true if given an existing file and fills file_info', -> - file_info = file_info_new! - path = 'unit-test-directory/test.file' - assert.is_true (fs.os_get_file_info_link path, file_info) - assert.is_true (is_file_info_filled file_info) - - it 'returns the file info of the link, not the linked file', -> - file_info = file_info_new! - path = 'unit-test-directory/test_link.file' - assert.is_true (fs.os_get_file_info_link path, file_info) - assert.is_true (is_file_info_filled file_info) - mode = tonumber file_info[0].stat.st_mode - eq ffi.C.kS_IFLNK, (bit.band mode, ffi.C.kS_IFMT) - - describe 'os_get_file_info_fd', -> - it 'returns false if given an invalid file descriptor', -> - file_info = file_info_new! - assert.is_false (fs.os_get_file_info_fd -1, file_info) - - it 'returns true if given a file descriptor and fills file_info', -> - file_info = file_info_new! - path = 'unit-test-directory/test.file' - fd = ffi.C.open path, 0 - assert.is_true (fs.os_get_file_info_fd fd, file_info) - assert.is_true (is_file_info_filled file_info) - ffi.C.close fd - - describe 'os_file_info_id_equal', -> - it 'returns false if file infos represent different files', -> - file_info_1 = file_info_new! - file_info_2 = file_info_new! - path_1 = 'unit-test-directory/test.file' - path_2 = 'unit-test-directory/test_2.file' - assert.is_true (fs.os_get_file_info path_1, file_info_1) - assert.is_true (fs.os_get_file_info path_2, file_info_2) - assert.is_false (fs.os_file_info_id_equal file_info_1, file_info_2) - - it 'returns true if file infos represent the same file', -> - file_info_1 = file_info_new! - file_info_2 = file_info_new! - path = 'unit-test-directory/test.file' - assert.is_true (fs.os_get_file_info path, file_info_1) - assert.is_true (fs.os_get_file_info path, file_info_2) - assert.is_true (fs.os_file_info_id_equal file_info_1, file_info_2) - - it 'returns true if file infos represent the same file (symlink)', -> - file_info_1 = file_info_new! - file_info_2 = file_info_new! - path_1 = 'unit-test-directory/test.file' - path_2 = 'unit-test-directory/test_link.file' - assert.is_true (fs.os_get_file_info path_1, file_info_1) - assert.is_true (fs.os_get_file_info path_2, file_info_2) - assert.is_true (fs.os_file_info_id_equal file_info_1, file_info_2) - - describe 'os_file_info_get_id', -> - it 'extracts ino/dev from file_info into file_id', -> - file_info = file_info_new! - file_id = file_id_new! - path = 'unit-test-directory/test.file' - assert.is_true (fs.os_get_file_info path, file_info) - fs.os_file_info_get_id(file_info, file_id) - eq file_info[0].stat.st_ino, file_id[0].inode - eq file_info[0].stat.st_dev, file_id[0].device_id - - describe 'os_file_info_get_inode', -> - it 'returns the inode from file_info', -> - file_info = file_info_new! - path = 'unit-test-directory/test.file' - assert.is_true (fs.os_get_file_info path, file_info) - inode = fs.os_file_info_get_inode(file_info) - eq file_info[0].stat.st_ino, inode - - describe 'os_get_file_id', -> - it 'returns false if given an non-existing file', -> - file_id = file_id_new! - assert.is_false (fs.os_get_file_id '/non-existent', file_id) - - it 'returns true if given an existing file and fills file_id', -> - file_id = file_id_new! - path = 'unit-test-directory/test.file' - assert.is_true (fs.os_get_file_id path, file_id) - assert.is_true 0 < file_id[0].inode - assert.is_true 0 < file_id[0].device_id - - describe 'os_file_id_equal', -> - it 'returns true if two FileIDs are equal', -> - file_id = file_id_new! - path = 'unit-test-directory/test.file' - assert.is_true (fs.os_get_file_id path, file_id) - assert.is_true (fs.os_file_id_equal file_id, file_id) - - it 'returns false if two FileIDs are not equal', -> - file_id_1 = file_id_new! - file_id_2 = file_id_new! - path_1 = 'unit-test-directory/test.file' - path_2 = 'unit-test-directory/test_2.file' - assert.is_true (fs.os_get_file_id path_1, file_id_1) - assert.is_true (fs.os_get_file_id path_2, file_id_2) - assert.is_false (fs.os_file_id_equal file_id_1, file_id_2) - - describe 'os_file_id_equal_file_info', -> - it 'returns true if file_id and file_info represent the same file', -> - file_id = file_id_new! - file_info = file_info_new! - path = 'unit-test-directory/test.file' - assert.is_true (fs.os_get_file_id path, file_id) - assert.is_true (fs.os_get_file_info path, file_info) - assert.is_true (fs.os_file_id_equal_file_info file_id, file_info) - - it 'returns false if file_id and file_info represent different files',-> - file_id = file_id_new! - file_info = file_info_new! - path_1 = 'unit-test-directory/test.file' - path_2 = 'unit-test-directory/test_2.file' - assert.is_true (fs.os_get_file_id path_1, file_id) - assert.is_true (fs.os_get_file_info path_2, file_info) - assert.is_false (fs.os_file_id_equal_file_info file_id, file_info) - From df50d242f5915621d41fc4f634d5ea1b0c7863b0 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Sat, 30 Aug 2014 11:42:45 -0300 Subject: [PATCH 05/11] unittest: convert path_spec.moon to lua --- test/unit/path_spec.lua | 478 +++++++++++++++++++++++++++++++++++++++ test/unit/path_spec.moon | 383 ------------------------------- 2 files changed, 478 insertions(+), 383 deletions(-) create mode 100644 test/unit/path_spec.lua delete mode 100644 test/unit/path_spec.moon diff --git a/test/unit/path_spec.lua b/test/unit/path_spec.lua new file mode 100644 index 00000000000000..15888c71b99470 --- /dev/null +++ b/test/unit/path_spec.lua @@ -0,0 +1,478 @@ +local helpers = require('test.unit.helpers') + +local cimport = helpers.cimport +local internalize = helpers.internalize +local eq = helpers.eq +local neq = helpers.neq +local ffi = helpers.ffi +local lib = helpers.lib +local cstr = helpers.cstr +local to_cstr = helpers.to_cstr +local NULL = helpers.NULL +local OK = helpers.OK +local FAIL = helpers.FAIL + +require('lfs') +local path = cimport('./src/nvim/path.h') + +-- import constants parsed by ffi +local kEqualFiles = path.kEqualFiles +local kDifferentFiles = path.kDifferentFiles +local kBothFilesMissing = path.kBothFilesMissing +local kOneFileMissing = path.kOneFileMissing +local kEqualFileNames = path.kEqualFileNames + +describe('path function', function() + describe('path_full_dir_name', function() + setup(function() + lfs.mkdir('unit-test-directory') + end) + + teardown(function() + lfs.rmdir('unit-test-directory') + end) + + function path_full_dir_name(directory, buffer, len) + directory = to_cstr(directory) + return path.path_full_dir_name(directory, buffer, len) + end + + before_each(function() + -- Create empty string buffer which will contain the resulting path. + len = (string.len(lfs.currentdir())) + 22 + buffer = cstr(len, '') + end) + + it('returns the absolute directory name of a given relative one', function() + local result = path_full_dir_name('..', buffer, len) + eq(OK, result) + local old_dir = lfs.currentdir() + lfs.chdir('..') + local expected = lfs.currentdir() + lfs.chdir(old_dir) + eq(expected, (ffi.string(buffer))) + end) + + it('returns the current directory name if the given string is empty', function() + eq(OK, (path_full_dir_name('', buffer, len))) + eq(lfs.currentdir(), (ffi.string(buffer))) + end) + + it('fails if the given directory does not exist', function() + eq(FAIL, path_full_dir_name('does_not_exist', buffer, len)) + end) + + it('works with a normal relative dir', function() + local result = path_full_dir_name('unit-test-directory', buffer, len) + eq(lfs.currentdir() .. '/unit-test-directory', (ffi.string(buffer))) + eq(OK, result) + end) + end) + + describe('path_full_compare', function() + function path_full_compare(s1, s2, cn) + s1 = to_cstr(s1) + s2 = to_cstr(s2) + return path.path_full_compare(s1, s2, cn or 0) + end + + local f1 = 'f1.o' + local f2 = 'f2.o' + before_each(function() + -- create the three files that will be used in this spec + io.open(f1, 'w').close() + io.open(f2, 'w').close() + end) + + after_each(function() + os.remove(f1) + os.remove(f2) + end) + + it('returns kEqualFiles when passed the same file', function() + eq(kEqualFiles, (path_full_compare(f1, f1))) + end) + + it('returns kEqualFileNames when files that dont exist and have same name', function() + eq(kEqualFileNames, (path_full_compare('null.txt', 'null.txt', true))) + end) + + it('returns kBothFilesMissing when files that dont exist', function() + eq(kBothFilesMissing, (path_full_compare('null.txt', 'null.txt'))) + end) + + it('returns kDifferentFiles when passed different files', function() + eq(kDifferentFiles, (path_full_compare(f1, f2))) + eq(kDifferentFiles, (path_full_compare(f2, f1))) + end) + + it('returns kOneFileMissing if only one does not exist', function() + eq(kOneFileMissing, (path_full_compare(f1, 'null.txt'))) + eq(kOneFileMissing, (path_full_compare('null.txt', f1))) + end) + end) + + describe('path_tail', function() + function path_tail(file) + local res = path.path_tail((to_cstr(file))) + neq(NULL, res) + return ffi.string(res) + end + + it('returns the tail of a given file path', function() + eq('file.txt', path_tail('directory/file.txt')) + end) + + it('returns an empty string if file ends in a slash', function() + eq('', path_tail('directory/')) + end) + end) + + describe('path_tail_with_sep', function() + function path_tail_with_sep(file) + local res = path.path_tail_with_sep((to_cstr(file))) + neq(NULL, res) + return ffi.string(res) + end + + it('returns the tail of a file together with its separator', function() + eq('///file.txt', path_tail_with_sep('directory///file.txt')) + end) + + it('returns an empty string when given an empty file name', function() + eq('', path_tail_with_sep('')) + end) + + it('returns only the separator if there is a trailing separator', function() + eq('/', path_tail_with_sep('some/directory/')) + end) + + it('cuts a leading separator', function() + eq('file.txt', path_tail_with_sep('/file.txt')) + eq('', path_tail_with_sep('/')) + end) + + it('returns the whole file name if there is no separator', function() + eq('file.txt', path_tail_with_sep('file.txt')) + end) + end) + + describe('invocation_path_tail', function() + -- Returns the path tail and length (out param) of the tail. + -- Does not convert the tail from C-pointer to lua string for use with + -- strcmp. + function invocation_path_tail(invk) + local plen = ffi.new('size_t[?]', 1) + local ptail = path.invocation_path_tail((to_cstr(invk)), plen) + neq(NULL, ptail) + + -- it does not change the output if len==NULL + local tail2 = path.invocation_path_tail((to_cstr(invk)), NULL) + neq(NULL, tail2) + eq((ffi.string(ptail)), (ffi.string(tail2))) + return ptail, plen[0] + end + + -- This test mimics the intended use in C. + function compare(base, pinvk, len) + return eq(0, (ffi.C.strncmp((to_cstr(base)), pinvk, len))) + end + + it('returns the executable name of an invocation given a relative invocation', function() + local invk, len = invocation_path_tail('directory/exe a b c') + compare("exe a b c", invk, len) + eq(3, len) + end) + + it('returns the executable name of an invocation given an absolute invocation', function() + if ffi.os == 'Windows' then + local invk, len = invocation_path_tail('C:\\Users\\anyone\\Program Files\\z a b') + compare('z a b', invk, len) + eq(1, len) + else + local invk, len = invocation_path_tail('/usr/bin/z a b') + compare('z a b', invk, len) + eq(1, len) + end + end) + + it('does not count arguments to the executable as part of its path', function() + local invk, len = invocation_path_tail('exe a/b\\c') + compare("exe a/b\\c", invk, len) + eq(3, len) + end) + + it('only accepts whitespace as a terminator for the executable name', function() + local invk, len = invocation_path_tail('exe-a+b_c[]()|#!@$%^&*') + eq('exe-a+b_c[]()|#!@$%^&*', (ffi.string(invk))) + end) + + it('is equivalent to path_tail when args do not contain a path separator', function() + local ptail = path.path_tail(to_cstr("a/b/c x y z")) + neq(NULL, ptail) + local tail = ffi.string(ptail) + local invk, len = invocation_path_tail("a/b/c x y z") + eq(tail, ffi.string(invk)) + end) + + it('is not equivalent to path_tail when args contain a path separator', function() + local ptail = path.path_tail(to_cstr("a/b/c x y/z")) + neq(NULL, ptail) + local invk, len = invocation_path_tail("a/b/c x y/z") + neq((ffi.string(ptail)), (ffi.string(invk))) + end) + end) + + describe('path_next_component', function() + function path_next_component(file) + local res = path.path_next_component((to_cstr(file))) + neq(NULL, res) + return ffi.string(res) + end + + it('returns', function() + eq('directory/file.txt', path_next_component('some/directory/file.txt')) + end) + + it('returns empty string if given file contains no separator', function() + eq('', path_next_component('file.txt')) + end) + end) + + describe('path_shorten_fname', function() + it('returns NULL if `full_path` is NULL', function() + local dir = to_cstr('some/directory/file.txt') + eq(NULL, (path.path_shorten_fname(NULL, dir))) + end) + + it('returns NULL if the path and dir does not match', function() + local dir = to_cstr('not/the/same') + local full = to_cstr('as/this.txt') + eq(NULL, (path.path_shorten_fname(full, dir))) + end) + + it('returns NULL if the path is not separated properly', function() + local dir = to_cstr('some/very/long/') + local full = to_cstr('some/very/long/directory/file.txt') + eq(NULL, (path.path_shorten_fname(full, dir))) + end) + + it('shortens the filename if `dir_name` is the start of `full_path`', function() + local full = to_cstr('some/very/long/directory/file.txt') + local dir = to_cstr('some/very/long') + eq('directory/file.txt', (ffi.string(path.path_shorten_fname(full, dir)))) + end) + end) +end) + +describe('path_shorten_fname_if_possible', function() + local cwd = lfs.currentdir() + + before_each(function() + lfs.mkdir('ut_directory') + end) + + after_each(function() + lfs.chdir(cwd) + lfs.rmdir('ut_directory') + end) + + describe('path_shorten_fname_if_possible', function() + it('returns shortened path if possible', function() + lfs.chdir('ut_directory') + local full = to_cstr(lfs.currentdir() .. '/subdir/file.txt') + eq('subdir/file.txt', (ffi.string(path.path_shorten_fname_if_possible(full)))) + end) + + it('returns `full_path` if a shorter version is not possible', function() + local old = lfs.currentdir() + lfs.chdir('ut_directory') + local full = old .. '/subdir/file.txt' + eq(full, (ffi.string(path.path_shorten_fname_if_possible(to_cstr(full))))) + end) + + it('returns NULL if `full_path` is NULL', function() + eq(NULL, (path.path_shorten_fname_if_possible(NULL))) + end) + end) +end) + +describe('more path function', function() + setup(function() + lfs.mkdir('unit-test-directory'); + io.open('unit-test-directory/test.file', 'w').close() + + -- Since the tests are executed, they are called by an executable. We use + -- that executable for several asserts. + absolute_executable = arg[0] + + -- Split absolute_executable into a directory and the actual file name for + -- later usage. + directory, executable_name = string.match(absolute_executable, '^(.*)/(.*)$') + end) + + teardown(function() + os.remove('unit-test-directory/test.file') + lfs.rmdir('unit-test-directory') + end) + + describe('vim_FullName', function() + function vim_FullName(filename, buffer, length, force) + filename = to_cstr(filename) + return path.vim_FullName(filename, buffer, length, force) + end + + before_each(function() + -- Create empty string buffer which will contain the resulting path. + len = (string.len(lfs.currentdir())) + 33 + buffer = cstr(len, '') + end) + + it('fails if given filename is NULL', function() + local force_expansion = 1 + local result = path.vim_FullName(NULL, buffer, len, force_expansion) + eq(FAIL, result) + end) + + it('uses the filename if the filename is a URL', function() + local force_expansion = 1 + local filename = 'http://www.neovim.org' + local result = vim_FullName(filename, buffer, len, force_expansion) + eq(filename, (ffi.string(buffer))) + eq(OK, result) + end) + + it('fails and uses filename if given filename contains non-existing directory', function() + local force_expansion = 1 + local filename = 'non_existing_dir/test.file' + local result = vim_FullName(filename, buffer, len, force_expansion) + eq(filename, (ffi.string(buffer))) + eq(FAIL, result) + end) + + it('concatenates given filename if it does not contain a slash', function() + local force_expansion = 1 + local result = vim_FullName('test.file', buffer, len, force_expansion) + local expected = lfs.currentdir() .. '/test.file' + eq(expected, (ffi.string(buffer))) + eq(OK, result) + end) + + it('concatenates given filename if it is a directory but does not contain a\n slash', function() + local force_expansion = 1 + local result = vim_FullName('..', buffer, len, force_expansion) + local expected = lfs.currentdir() .. '/..' + eq(expected, (ffi.string(buffer))) + eq(OK, result) + end) + + -- Is it possible for every developer to enter '..' directory while running + -- the unit tests? Which other directory would be better? + it('enters given directory (instead of just concatenating the strings) if possible and if path contains a slash', function() + local force_expansion = 1 + local result = vim_FullName('../test.file', buffer, len, force_expansion) + local old_dir = lfs.currentdir() + lfs.chdir('..') + local expected = lfs.currentdir() .. '/test.file' + lfs.chdir(old_dir) + eq(expected, (ffi.string(buffer))) + eq(OK, result) + end) + + it('just copies the path if it is already absolute and force=0', function() + local force_expansion = 0 + local absolute_path = '/absolute/path' + local result = vim_FullName(absolute_path, buffer, len, force_expansion) + eq(absolute_path, (ffi.string(buffer))) + eq(OK, result) + end) + + it('fails and uses filename when the path is relative to HOME', function() + local force_expansion = 1 + local absolute_path = '~/home.file' + local result = vim_FullName(absolute_path, buffer, len, force_expansion) + eq(absolute_path, (ffi.string(buffer))) + eq(FAIL, result) + end) + + it('works with some "normal" relative path with directories', function() + local force_expansion = 1 + local result = vim_FullName('unit-test-directory/test.file', buffer, len, force_expansion) + eq(OK, result) + eq(lfs.currentdir() .. '/unit-test-directory/test.file', (ffi.string(buffer))) + end) + + it('does not modify the given filename', function() + local force_expansion = 1 + local filename = to_cstr('unit-test-directory/test.file') + -- Don't use the wrapper here but pass a cstring directly to the c + -- function. + local result = path.vim_FullName(filename, buffer, len, force_expansion) + eq(lfs.currentdir() .. '/unit-test-directory/test.file', (ffi.string(buffer))) + eq('unit-test-directory/test.file', (ffi.string(filename))) + eq(OK, result) + end) + end) + + describe('append_path', function() + it('joins given paths with a slash', function() + local path1 = cstr(100, 'path1') + local to_append = to_cstr('path2') + eq(OK, (path.append_path(path1, to_append, 100))) + eq("path1/path2", (ffi.string(path1))) + end) + + it('joins given paths without adding an unnecessary slash', function() + local path1 = cstr(100, 'path1/') + local to_append = to_cstr('path2') + eq(OK, path.append_path(path1, to_append, 100)) + eq("path1/path2", (ffi.string(path1))) + end) + + it('fails and uses filename if there is not enough space left for to_append', function() + local path1 = cstr(11, 'path1/') + local to_append = to_cstr('path2') + eq(FAIL, (path.append_path(path1, to_append, 11))) + end) + + it('does not append a slash if to_append is empty', function() + local path1 = cstr(6, 'path1') + local to_append = to_cstr('') + eq(OK, (path.append_path(path1, to_append, 6))) + eq('path1', (ffi.string(path1))) + end) + + it('does not append unnecessary dots', function() + local path1 = cstr(6, 'path1') + local to_append = to_cstr('.') + eq(OK, (path.append_path(path1, to_append, 6))) + eq('path1', (ffi.string(path1))) + end) + + it('copies to_append to path, if path is empty', function() + local path1 = cstr(7, '') + local to_append = to_cstr('/path2') + eq(OK, (path.append_path(path1, to_append, 7))) + eq('/path2', (ffi.string(path1))) + end) + end) + + describe('path_is_absolute_path', function() + function path_is_absolute_path(filename) + filename = to_cstr(filename) + return path.path_is_absolute_path(filename) + end + + it('returns true if filename starts with a slash', function() + eq(OK, path_is_absolute_path('/some/directory/')) + end) + + it('returns true if filename starts with a tilde', function() + eq(OK, path_is_absolute_path('~/in/my/home~/directory')) + end) + + it('returns false if filename starts not with slash nor tilde', function() + eq(FAIL, path_is_absolute_path('not/in/my/home~/directory')) + end) + end) +end) diff --git a/test/unit/path_spec.moon b/test/unit/path_spec.moon deleted file mode 100644 index f7e5ed00bcaed8..00000000000000 --- a/test/unit/path_spec.moon +++ /dev/null @@ -1,383 +0,0 @@ -{:cimport, :internalize, :eq, :neq, :ffi, :lib, :cstr, :to_cstr, :NULL, :OK, :FAIL} = require 'test.unit.helpers' -require 'lfs' - -path = cimport './src/nvim/path.h' - --- import constants parsed by ffi -{:kEqualFiles, :kDifferentFiles, :kBothFilesMissing, :kOneFileMissing, :kEqualFileNames} = path - -describe 'path function', -> - describe 'path_full_dir_name', -> - setup -> - lfs.mkdir 'unit-test-directory' - - teardown -> - lfs.rmdir 'unit-test-directory' - - path_full_dir_name = (directory, buffer, len) -> - directory = to_cstr directory - path.path_full_dir_name directory, buffer, len - - before_each -> - -- Create empty string buffer which will contain the resulting path. - export len = (string.len lfs.currentdir!) + 22 - export buffer = cstr len, '' - - it 'returns the absolute directory name of a given relative one', -> - result = path_full_dir_name '..', buffer, len - eq OK, result - old_dir = lfs.currentdir! - lfs.chdir '..' - expected = lfs.currentdir! - lfs.chdir old_dir - eq expected, (ffi.string buffer) - - it 'returns the current directory name if the given string is empty', -> - eq OK, (path_full_dir_name '', buffer, len) - eq lfs.currentdir!, (ffi.string buffer) - - it 'fails if the given directory does not exist', -> - eq FAIL, path_full_dir_name('does_not_exist', buffer, len) - - it 'works with a normal relative dir', -> - result = path_full_dir_name('unit-test-directory', buffer, len) - eq lfs.currentdir! .. '/unit-test-directory', (ffi.string buffer) - eq OK, result - - describe 'path_full_compare', -> - - path_full_compare = (s1, s2, cn) -> - s1 = to_cstr s1 - s2 = to_cstr s2 - path.path_full_compare s1, s2, cn or 0 - - f1 = 'f1.o' - f2 = 'f2.o' - - before_each -> - -- create the three files that will be used in this spec - (io.open f1, 'w').close! - (io.open f2, 'w').close! - - after_each -> - os.remove f1 - os.remove f2 - - it 'returns kEqualFiles when passed the same file', -> - eq kEqualFiles, (path_full_compare f1, f1) - - it 'returns kEqualFileNames when files that dont exist and have same name', -> - eq kEqualFileNames, (path_full_compare 'null.txt', 'null.txt', true) - - it 'returns kBothFilesMissing when files that dont exist', -> - eq kBothFilesMissing, (path_full_compare 'null.txt', 'null.txt') - - it 'returns kDifferentFiles when passed different files', -> - eq kDifferentFiles, (path_full_compare f1, f2) - eq kDifferentFiles, (path_full_compare f2, f1) - - it 'returns kOneFileMissing if only one does not exist', -> - eq kOneFileMissing, (path_full_compare f1, 'null.txt') - eq kOneFileMissing, (path_full_compare 'null.txt', f1) - - describe 'path_tail', -> - path_tail = (file) -> - res = path.path_tail (to_cstr file) - neq NULL, res - ffi.string res - - it 'returns the tail of a given file path', -> - eq 'file.txt', path_tail 'directory/file.txt' - - it 'returns an empty string if file ends in a slash', -> - eq '', path_tail 'directory/' - - describe 'path_tail_with_sep', -> - path_tail_with_sep = (file) -> - res = path.path_tail_with_sep (to_cstr file) - neq NULL, res - ffi.string res - - it 'returns the tail of a file together with its separator', -> - eq '///file.txt', path_tail_with_sep 'directory///file.txt' - - it 'returns an empty string when given an empty file name', -> - eq '', path_tail_with_sep '' - - it 'returns only the separator if there is a trailing separator', -> - eq '/', path_tail_with_sep 'some/directory/' - - it 'cuts a leading separator', -> - eq 'file.txt', path_tail_with_sep '/file.txt' - eq '', path_tail_with_sep '/' - - it 'returns the whole file name if there is no separator', -> - eq 'file.txt', path_tail_with_sep 'file.txt' - - describe 'invocation_path_tail', -> - -- Returns the path tail and length (out param) of the tail. - -- Does not convert the tail from C-pointer to lua string for use with - -- strcmp. - invocation_path_tail = (invk) -> - plen = ffi.new 'size_t[?]', 1 - ptail = path.invocation_path_tail (to_cstr invk), plen - neq NULL, ptail - - -- it does not change the output if len==NULL - tail2 = path.invocation_path_tail (to_cstr invk), NULL - neq NULL, tail2 - eq (ffi.string ptail), (ffi.string tail2) - - ptail, plen[0] - - -- This test mimics the intended use in C. - compare = (base, pinvk, len) -> - eq 0, (ffi.C.strncmp (to_cstr base), pinvk, len) - - it 'returns the executable name of an invocation given a relative invocation', -> - invk, len = invocation_path_tail 'directory/exe a b c' - compare "exe a b c", invk, len - eq 3, len - - it 'returns the executable name of an invocation given an absolute invocation', -> - if ffi.os == 'Windows' - invk, len = invocation_path_tail 'C:\\Users\\anyone\\Program Files\\z a b' - compare 'z a b', invk, len - eq 1, len - else - invk, len = invocation_path_tail '/usr/bin/z a b' - compare 'z a b', invk, len - eq 1, len - - it 'does not count arguments to the executable as part of its path', -> - invk, len = invocation_path_tail 'exe a/b\\c' - compare "exe a/b\\c", invk, len - eq 3, len - - it 'only accepts whitespace as a terminator for the executable name', -> - invk, len = invocation_path_tail 'exe-a+b_c[]()|#!@$%^&*' - eq 'exe-a+b_c[]()|#!@$%^&*', (ffi.string invk) - - it 'is equivalent to path_tail when args do not contain a path separator', -> - ptail = path.path_tail to_cstr "a/b/c x y z" - neq NULL, ptail - tail = ffi.string ptail - - invk, len = invocation_path_tail "a/b/c x y z" - eq tail, ffi.string invk - - it 'is not equivalent to path_tail when args contain a path separator', -> - ptail = path.path_tail to_cstr "a/b/c x y/z" - neq NULL, ptail - - invk, len = invocation_path_tail "a/b/c x y/z" - neq (ffi.string ptail), (ffi.string invk) - - describe 'path_next_component', -> - path_next_component = (file) -> - res = path.path_next_component (to_cstr file) - neq NULL, res - ffi.string res - - it 'returns', -> - eq 'directory/file.txt', path_next_component 'some/directory/file.txt' - - it 'returns empty string if given file contains no separator', -> - eq '', path_next_component 'file.txt' - - describe 'path_shorten_fname', -> - it 'returns NULL if `full_path` is NULL', -> - dir = to_cstr 'some/directory/file.txt' - eq NULL, (path.path_shorten_fname NULL, dir) - - it 'returns NULL if the path and dir does not match', -> - dir = to_cstr 'not/the/same' - full = to_cstr 'as/this.txt' - eq NULL, (path.path_shorten_fname full, dir) - - it 'returns NULL if the path is not separated properly', -> - dir = to_cstr 'some/very/long/' - full = to_cstr 'some/very/long/directory/file.txt' - eq NULL, (path.path_shorten_fname full, dir) - - it 'shortens the filename if `dir_name` is the start of `full_path`', -> - full = to_cstr 'some/very/long/directory/file.txt' - dir = to_cstr 'some/very/long' - eq 'directory/file.txt', (ffi.string path.path_shorten_fname full, dir) - -describe 'path_shorten_fname_if_possible', -> - cwd = lfs.currentdir! - before_each -> - lfs.mkdir 'ut_directory' - after_each -> - lfs.chdir cwd - lfs.rmdir 'ut_directory' - - describe 'path_shorten_fname_if_possible', -> - it 'returns shortened path if possible', -> - lfs.chdir 'ut_directory' - full = to_cstr lfs.currentdir! .. '/subdir/file.txt' - eq 'subdir/file.txt', (ffi.string path.path_shorten_fname_if_possible full) - - it 'returns `full_path` if a shorter version is not possible', -> - old = lfs.currentdir! - lfs.chdir 'ut_directory' - full = old .. '/subdir/file.txt' - eq full, (ffi.string path.path_shorten_fname_if_possible to_cstr full) - - it 'returns NULL if `full_path` is NULL', -> - eq NULL, (path.path_shorten_fname_if_possible NULL) - -describe 'more path function', -> - setup -> - lfs.mkdir 'unit-test-directory' - (io.open 'unit-test-directory/test.file', 'w').close! - - -- Since the tests are executed, they are called by an executable. We use - -- that executable for several asserts. - export absolute_executable = arg[0] - - -- Split absolute_executable into a directory and the actual file name for - -- later usage. - export directory, executable_name = string.match(absolute_executable, '^(.*)/(.*)$') - - teardown -> - os.remove 'unit-test-directory/test.file' - lfs.rmdir 'unit-test-directory' - - describe 'vim_FullName', -> - vim_FullName = (filename, buffer, length, force) -> - filename = to_cstr filename - path.vim_FullName filename, buffer, length, force - - before_each -> - -- Create empty string buffer which will contain the resulting path. - export len = (string.len lfs.currentdir!) + 33 - export buffer = cstr len, '' - - it 'fails if given filename is NULL', -> - force_expansion = 1 - result = path.vim_FullName NULL, buffer, len, force_expansion - eq FAIL, result - - it 'uses the filename if the filename is a URL', -> - force_expansion = 1 - filename = 'http://www.neovim.org' - result = vim_FullName filename, buffer, len, force_expansion - eq filename, (ffi.string buffer) - eq OK, result - - it 'fails and uses filename if given filename contains non-existing directory', -> - force_expansion = 1 - filename = 'non_existing_dir/test.file' - result = vim_FullName filename, buffer, len, force_expansion - eq filename, (ffi.string buffer) - eq FAIL, result - - it 'concatenates given filename if it does not contain a slash', -> - force_expansion = 1 - result = vim_FullName 'test.file', buffer, len, force_expansion - expected = lfs.currentdir! .. '/test.file' - eq expected, (ffi.string buffer) - eq OK, result - - it 'concatenates given filename if it is a directory but does not contain a - slash', -> - force_expansion = 1 - result = vim_FullName '..', buffer, len, force_expansion - expected = lfs.currentdir! .. '/..' - eq expected, (ffi.string buffer) - eq OK, result - - -- Is it possible for every developer to enter '..' directory while running - -- the unit tests? Which other directory would be better? - it 'enters given directory (instead of just concatenating the strings) if - possible and if path contains a slash', -> - force_expansion = 1 - result = vim_FullName '../test.file', buffer, len, force_expansion - old_dir = lfs.currentdir! - lfs.chdir '..' - expected = lfs.currentdir! .. '/test.file' - lfs.chdir old_dir - eq expected, (ffi.string buffer) - eq OK, result - - it 'just copies the path if it is already absolute and force=0', -> - force_expansion = 0 - absolute_path = '/absolute/path' - result = vim_FullName absolute_path, buffer, len, force_expansion - eq absolute_path, (ffi.string buffer) - eq OK, result - - it 'fails and uses filename when the path is relative to HOME', -> - force_expansion = 1 - absolute_path = '~/home.file' - result = vim_FullName absolute_path, buffer, len, force_expansion - eq absolute_path, (ffi.string buffer) - eq FAIL, result - - it 'works with some "normal" relative path with directories', -> - force_expansion = 1 - result = vim_FullName 'unit-test-directory/test.file', buffer, len, force_expansion - eq OK, result - eq lfs.currentdir! .. '/unit-test-directory/test.file', (ffi.string buffer) - - it 'does not modify the given filename', -> - force_expansion = 1 - filename = to_cstr 'unit-test-directory/test.file' - -- Don't use the wrapper here but pass a cstring directly to the c - -- function. - result = path.vim_FullName filename, buffer, len, force_expansion - eq lfs.currentdir! .. '/unit-test-directory/test.file', (ffi.string buffer) - eq 'unit-test-directory/test.file', (ffi.string filename) - eq OK, result - - describe 'append_path', -> - it 'joins given paths with a slash', -> - path1 = cstr 100, 'path1' - to_append = to_cstr 'path2' - eq OK, (path.append_path path1, to_append, 100) - eq "path1/path2", (ffi.string path1) - - it 'joins given paths without adding an unnecessary slash', -> - path1 = cstr 100, 'path1/' - to_append = to_cstr 'path2' - eq OK, path.append_path path1, to_append, 100 - eq "path1/path2", (ffi.string path1) - - it 'fails and uses filename if there is not enough space left for to_append', -> - path1 = cstr 11, 'path1/' - to_append = to_cstr 'path2' - eq FAIL, (path.append_path path1, to_append, 11) - - it 'does not append a slash if to_append is empty', -> - path1 = cstr 6, 'path1' - to_append = to_cstr '' - eq OK, (path.append_path path1, to_append, 6) - eq 'path1', (ffi.string path1) - - it 'does not append unnecessary dots', -> - path1 = cstr 6, 'path1' - to_append = to_cstr '.' - eq OK, (path.append_path path1, to_append, 6) - eq 'path1', (ffi.string path1) - - it 'copies to_append to path, if path is empty', -> - path1 = cstr 7, '' - to_append = to_cstr '/path2' - eq OK, (path.append_path path1, to_append, 7) - eq '/path2', (ffi.string path1) - - describe 'path_is_absolute_path', -> - path_is_absolute_path = (filename) -> - filename = to_cstr filename - path.path_is_absolute_path filename - - it 'returns true if filename starts with a slash', -> - eq OK, path_is_absolute_path '/some/directory/' - - it 'returns true if filename starts with a tilde', -> - eq OK, path_is_absolute_path '~/in/my/home~/directory' - - it 'returns false if filename starts not with slash nor tilde', -> - eq FAIL, path_is_absolute_path 'not/in/my/home~/directory' From d74ae5be9de985eee3e5026ffe0c639161ce82fc Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Sat, 30 Aug 2014 11:47:55 -0300 Subject: [PATCH 06/11] unittest: convert users_spec.moon to lua --- test/unit/os/users_spec.lua | 91 ++++++++++++++++++++++++++++++++++++ test/unit/os/users_spec.moon | 70 --------------------------- 2 files changed, 91 insertions(+), 70 deletions(-) create mode 100644 test/unit/os/users_spec.lua delete mode 100644 test/unit/os/users_spec.moon diff --git a/test/unit/os/users_spec.lua b/test/unit/os/users_spec.lua new file mode 100644 index 00000000000000..df5d2365c6f592 --- /dev/null +++ b/test/unit/os/users_spec.lua @@ -0,0 +1,91 @@ +local helpers = require('test.unit.helpers') + +local cimport = helpers.cimport +local internalize = helpers.internalize +local eq = helpers.eq +local ffi = helpers.ffi +local lib = helpers.lib +local cstr = helpers.cstr +local NULL = helpers.NULL +local OK = helpers.OK +local FAIL = helpers.FAIL + +local users = cimport('./src/nvim/os/os.h', 'unistd.h') + +function garray_new() + return ffi.new('garray_T[1]') +end + +function garray_get_len(array) + return array[0].ga_len +end + +function garray_get_item(array, index) + return (ffi.cast('void **', array[0].ga_data))[index] +end + +describe('users function', function() + -- will probably not work on windows + local current_username = os.getenv('USER') + + describe('os_get_usernames', function() + it('returns FAIL if called with NULL', function() + eq(FAIL, users.os_get_usernames(NULL)) + end) + + it('fills the names garray with os usernames and returns OK', function() + local ga_users = garray_new() + eq(OK, users.os_get_usernames(ga_users)) + local user_count = garray_get_len(ga_users) + assert.is_true(user_count > 0) + local current_username_found = false + for i = 0, user_count - 1 do + local name = ffi.string((garray_get_item(ga_users, i))) + if name == current_username then + current_username_found = true + end + end + assert.is_true(current_username_found) + end) + end) + + describe('os_get_user_name', function() + it('should write the username into the buffer and return OK', function() + local name_out = ffi.new('char[100]') + eq(OK, users.os_get_user_name(name_out, 100)) + eq(current_username, ffi.string(name_out)) + end) + end) + + describe('os_get_uname', function() + it('should write the username into the buffer and return OK', function() + local name_out = ffi.new('char[100]') + local user_id = lib.getuid() + eq(OK, users.os_get_uname(user_id, name_out, 100)) + eq(current_username, ffi.string(name_out)) + end) + + it('should FAIL if the userid is not found', function() + local name_out = ffi.new('char[100]') + -- hoping nobody has this uid + local user_id = 2342 + eq(FAIL, users.os_get_uname(user_id, name_out, 100)) + eq('2342', ffi.string(name_out)) + end) + end) + + describe('os_get_user_directory', function() + it('should return NULL if called with NULL', function() + eq(NULL, users.os_get_user_directory(NULL)) + end) + + it('should return $HOME for the current user', function() + local home = os.getenv('HOME') + eq(home, ffi.string((users.os_get_user_directory(current_username)))) + end) + + it('should return NULL if the user is not found', function() + eq(NULL, users.os_get_user_directory('neovim_user_not_found_test')) + end) + end) +end) diff --git a/test/unit/os/users_spec.moon b/test/unit/os/users_spec.moon deleted file mode 100644 index 35c23ccc6fccf1..00000000000000 --- a/test/unit/os/users_spec.moon +++ /dev/null @@ -1,70 +0,0 @@ -{:cimport, :internalize, :eq, :ffi, :lib, :cstr, :NULL, :OK, :FAIL} = require 'test.unit.helpers' - -users = cimport './src/nvim/os/os.h', 'unistd.h' - -garray_new = () -> - ffi.new 'garray_T[1]' - -garray_get_len = (array) -> - array[0].ga_len - -garray_get_item = (array, index) -> - (ffi.cast 'void **', array[0].ga_data)[index] - - -describe 'users function', -> - - -- will probably not work on windows - current_username = os.getenv 'USER' - - describe 'os_get_usernames', -> - - it 'returns FAIL if called with NULL', -> - eq FAIL, users.os_get_usernames NULL - - it 'fills the names garray with os usernames and returns OK', -> - ga_users = garray_new! - eq OK, users.os_get_usernames ga_users - user_count = garray_get_len ga_users - assert.is_true user_count > 0 - current_username_found = false - for i = 0, user_count - 1 - name = ffi.string (garray_get_item ga_users, i) - if name == current_username - current_username_found = true - assert.is_true current_username_found - - describe 'os_get_user_name', -> - - it 'should write the username into the buffer and return OK', -> - name_out = ffi.new 'char[100]' - eq OK, users.os_get_user_name(name_out, 100) - eq current_username, ffi.string name_out - - describe 'os_get_uname', -> - - it 'should write the username into the buffer and return OK', -> - name_out = ffi.new 'char[100]' - user_id = lib.getuid! - eq OK, users.os_get_uname(user_id, name_out, 100) - eq current_username, ffi.string name_out - - it 'should FAIL if the userid is not found', -> - name_out = ffi.new 'char[100]' - -- hoping nobody has this uid - user_id = 2342 - eq FAIL, users.os_get_uname(user_id, name_out, 100) - eq '2342', ffi.string name_out - - describe 'os_get_user_directory', -> - - it 'should return NULL if called with NULL', -> - eq NULL, users.os_get_user_directory NULL - - it 'should return $HOME for the current user', -> - home = os.getenv('HOME') - eq home, ffi.string (users.os_get_user_directory current_username) - - it 'should return NULL if the user is not found', -> - eq NULL, users.os_get_user_directory 'neovim_user_not_found_test' - From 3a951112c099fc1b93766fabccd39cc65238cac5 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Sat, 30 Aug 2014 11:56:52 -0300 Subject: [PATCH 07/11] unittest: convert preprocess.moon to lua --- test/unit/preprocess.lua | 198 ++++++++++++++++++++++++++++++++++++++ test/unit/preprocess.moon | 158 ------------------------------ 2 files changed, 198 insertions(+), 158 deletions(-) create mode 100644 test/unit/preprocess.lua delete mode 100644 test/unit/preprocess.moon diff --git a/test/unit/preprocess.lua b/test/unit/preprocess.lua new file mode 100644 index 00000000000000..240527032809cc --- /dev/null +++ b/test/unit/preprocess.lua @@ -0,0 +1,198 @@ +-- helps managing loading different headers into the LuaJIT ffi. Untested on +-- windows, will probably need quite a bit of adjustment to run there. + +local ffi = require("ffi") + +local ccs = {} + +local env_cc = os.getenv("CC") +if env_cc then + table.insert(ccs, {path = "/usr/bin/env " .. tostring(env_cc), type = "gcc"}) +end + +if ffi.os == "Windows" then + table.insert(ccs, {path = "cl", type = "msvc"}) +end + +table.insert(ccs, {path = "/usr/bin/env cc", type = "gcc"}) +table.insert(ccs, {path = "/usr/bin/env gcc", type = "gcc"}) +table.insert(ccs, {path = "/usr/bin/env gcc-4.9", type = "gcc"}) +table.insert(ccs, {path = "/usr/bin/env gcc-4.8", type = "gcc"}) +table.insert(ccs, {path = "/usr/bin/env gcc-4.7", type = "gcc"}) +table.insert(ccs, {path = "/usr/bin/env clang", type = "clang"}) +table.insert(ccs, {path = "/usr/bin/env icc", type = "gcc"}) + +local quote_me = '[^%w%+%-%=%@%_%/]' -- complement (needn't quote) +function shell_quote(str) + if string.find(str, quote_me) or str == '' then + return "'" .. string.gsub(str, "'", [['"'"']]) .. "'" + else + return str + end +end + +-- parse Makefile format dependencies into a Lua table +function parse_make_deps(deps) + -- remove line breaks and line concatenators + deps = deps:gsub("\n", ""):gsub("\\", "") + -- remove the Makefile "target:" element + deps = deps:gsub(".+:", "") + -- remove redundant spaces + deps = deps:gsub(" +", " ") + + -- split according to token (space in this case) + local headers = {} + for token in deps:gmatch("[^%s]+") do + -- headers[token] = true + headers[#headers + 1] = token + end + + -- resolve path redirections (..) to normalize all paths + for i, v in ipairs(headers) do + -- double dots (..) + headers[i] = v:gsub("/[^/%s]+/%.%.", "") + -- single dot (.) + headers[i] = v:gsub("%./", "") + end + + return headers +end + +-- will produce a string that represents a meta C header file that includes +-- all the passed in headers. I.e.: +-- +-- headerize({"stdio.h", "math.h", true} +-- produces: +-- #include +-- #include +-- +-- headerize({"vim.h", "memory.h", false} +-- produces: +-- #include "vim.h" +-- #include "memory.h" +function headerize(headers, global) + local pre = '"' + local post = pre + if global then + pre = "<" + post = ">" + end + + local formatted = {} + for i = 1, #headers do + local hdr = headers[i] + formatted[#formatted + 1] = "#include " .. + tostring(pre) .. + tostring(hdr) .. + tostring(post) + end + + return table.concat(formatted, "\n") +end + +local Gcc = { + -- preprocessor flags that will hopefully make the compiler produce C + -- declarations that the LuaJIT ffi understands. + preprocessor_extra_flags = { + '-D "aligned(ARGS)="', + '-D "__attribute__(ARGS)="', + '-D "__asm(ARGS)="', + '-D "__asm__(ARGS)="', + '-D "__inline__="', + '-D "EXTERN=extern"', + '-D "INIT(...)="', + '-D_GNU_SOURCE', + '-DINCLUDE_GENERATED_DECLARATIONS' + } +} + +function Gcc:new(obj) + obj = obj or {} + setmetatable(obj, self) + self.__index = self + return obj +end + +function Gcc:add_to_include_path(...) + local paths = {...} + for i = 1, #paths do + local path = paths[i] + local directive = '-I ' .. '"' .. path .. '"' + local ef = self.preprocessor_extra_flags + ef[#ef + 1] = directive + end +end + +-- returns a list of the headers files upon which this file relies +function Gcc:dependencies(hdr) + local out = io.popen(tostring(self.path) .. " -M " .. tostring(hdr) .. " 2>&1") + local deps = out:read("*a") + out:close() + if deps then + return parse_make_deps(deps) + else + return nil + end +end + +-- returns a stream representing a preprocessed form of the passed-in headers. +-- Don't forget to close the stream by calling the close() method on it. +function Gcc:preprocess_stream(...) + -- create pseudo-header + local pseudoheader = headerize({...}, false) + local defines = table.concat(self.preprocessor_extra_flags, ' ') + local cmd = ("echo $hdr | " .. + tostring(self.path) .. + " " .. + tostring(defines) .. + " -std=c99 -P -E -"):gsub('$hdr', shell_quote(pseudoheader)) + -- lfs = require("lfs") + -- print("CWD: #{lfs.currentdir!}") + -- print("CMD: #{cmd}") + -- io.stderr\write("CWD: #{lfs.currentdir!}\n") + -- io.stderr\write("CMD: #{cmd}\n") + return io.popen(cmd) +end + +local Clang = Gcc:new() +local Msvc = Gcc:new() + +local type_to_class = { + ["gcc"] = Gcc, + ["clang"] = Clang, + ["msvc"] = Msvc +} + +-- find the best cc. If os.exec causes problems on windows (like popping up +-- a console window) we might consider using something like this: +-- http://scite-ru.googlecode.com/svn/trunk/pack/tools/LuaLib/shell.html#exec +function find_best_cc(ccs) + for _, meta in pairs(ccs) do + local version = io.popen(tostring(meta.path) .. " -v 2>&1") + version:close() + if version then + return type_to_class[meta.type]:new({path = meta.path}) + end + end + return nil +end + +-- find the best cc. If os.exec causes problems on windows (like popping up +-- a console window) we might consider using something like this: +-- http://scite-ru.googlecode.com/svn/trunk/pack/tools/LuaLib/shell.html#exec +local cc = nil +if cc == nil then + cc = find_best_cc(ccs) +end + +return { + includes = function(hdr) + return cc:dependencies(hdr) + end, + preprocess_stream = function(...) + return cc:preprocess_stream(...) + end, + add_to_include_path = function(...) + return cc:add_to_include_path(...) + end +} diff --git a/test/unit/preprocess.moon b/test/unit/preprocess.moon deleted file mode 100644 index cb734da2f753e4..00000000000000 --- a/test/unit/preprocess.moon +++ /dev/null @@ -1,158 +0,0 @@ --- helps managing loading different headers into the LuaJIT ffi. Untested on --- windows, will probably need quite a bit of adjustment to run there. - -ffi = require("ffi") - -ccs = {} - -env_cc = os.getenv("CC") -if env_cc - table.insert(ccs, {path: "/usr/bin/env #{env_cc}", type: "gcc"}) - -if ffi.os == "Windows" - table.insert(ccs, {path: "cl", type: "msvc"}) - -table.insert(ccs, {path: "/usr/bin/env cc", type: "gcc"}) -table.insert(ccs, {path: "/usr/bin/env gcc", type: "gcc"}) -table.insert(ccs, {path: "/usr/bin/env gcc-4.9", type: "gcc"}) -table.insert(ccs, {path: "/usr/bin/env gcc-4.8", type: "gcc"}) -table.insert(ccs, {path: "/usr/bin/env gcc-4.7", type: "gcc"}) -table.insert(ccs, {path: "/usr/bin/env clang", type: "clang"}) -table.insert(ccs, {path: "/usr/bin/env icc", type: "gcc"}) - -quote_me = '[^%w%+%-%=%@%_%/]' -- complement (needn't quote) -shell_quote = (str) -> - if string.find(str, quote_me) or str == '' then - "'" .. string.gsub(str, "'", [['"'"']]) .. "'" - else - str - --- parse Makefile format dependencies into a Lua table -parse_make_deps = (deps) -> - -- remove line breaks and line concatenators - deps = deps\gsub("\n", "")\gsub("\\", "") - - -- remove the Makefile "target:" element - deps = deps\gsub(".+:", "") - - -- remove redundant spaces - deps = deps\gsub(" +", " ") - - -- split according to token (space in this case) - headers = {} - for token in deps\gmatch("[^%s]+") - -- headers[token] = true - headers[#headers + 1] = token - - -- resolve path redirections (..) to normalize all paths - for i, v in ipairs(headers) - -- double dots (..) - headers[i] = v\gsub("/[^/%s]+/%.%.", "") - - -- single dot (.) - headers[i] = v\gsub("%./", "") - - headers - --- will produce a string that represents a meta C header file that includes --- all the passed in headers. I.e.: --- --- headerize({"stdio.h", "math.h", true} --- produces: --- #include --- #include --- --- headerize({"vim.h", "memory.h", false} --- produces: --- #include "vim.h" --- #include "memory.h" -headerize = (headers, global) -> - pre = '"' - post = pre - if global - pre = "<" - post = ">" - - formatted = ["#include #{pre}#{hdr}#{post}" for hdr in *headers] - table.concat(formatted, "\n") - -class Gcc - -- preprocessor flags that will hopefully make the compiler produce C - -- declarations that the LuaJIT ffi understands. - @@preprocessor_extra_flags = { - '-D "aligned(ARGS)="', - '-D "__attribute__(ARGS)="', - '-D "__asm(ARGS)="', - '-D "__asm__(ARGS)="', - '-D "__inline__="', - '-D "EXTERN=extern"', - '-D "INIT(...)="', - '-D_GNU_SOURCE', - '-DINCLUDE_GENERATED_DECLARATIONS' - } - - new: (path) => - @path = path - - add_to_include_path: (...) => - paths = {...} - for path in *paths - directive = '-I ' .. '"' .. path .. '"' - @@preprocessor_extra_flags[#@@preprocessor_extra_flags + 1] = directive - - -- returns a list of the headers files upon which this file relies - dependencies: (hdr) => - out = io.popen("#{@path} -M #{hdr} 2>&1") - deps = out\read("*a") - out\close! - - if deps - parse_make_deps(deps) - else - nil - - -- returns a stream representing a preprocessed form of the passed-in - -- headers. Don't forget to close the stream by calling the close() method - -- on it. - preprocess_stream: (...) => - paths = {...} - -- create pseudo-header - pseudoheader = headerize(paths, false) - defines = table.concat(@@preprocessor_extra_flags, ' ') - cmd = ("echo $hdr | #{@path} #{defines} -std=c99 -P -E -")\gsub('$hdr', shell_quote(pseudoheader)) - -- lfs = require("lfs") - -- print("CWD: #{lfs.currentdir!}") - -- print("CMD: #{cmd}") - -- io.stderr\write("CWD: #{lfs.currentdir!}\n") - -- io.stderr\write("CMD: #{cmd}\n") - io.popen(cmd) - -class Clang extends Gcc -class Msvc extends Gcc - -type_to_class = { - "gcc": Gcc, - "clang": Clang, - "msvc": Msvc -} - -find_best_cc = (ccs) -> - for _, meta in pairs(ccs) - version = io.popen("#{meta.path} -v 2>&1") - version\close! - if version - return type_to_class[meta.type](meta.path) - nil - --- find the best cc. If os.exec causes problems on windows (like popping up --- a console window) we might consider using something like this: --- http://scite-ru.googlecode.com/svn/trunk/pack/tools/LuaLib/shell.html#exec -cc = nil -if cc == nil - cc = find_best_cc(ccs) - -return { - includes: (hdr) -> cc\dependencies(hdr) - preprocess_stream: (...) -> cc\preprocess_stream(...) - add_to_include_path: (...) -> cc\add_to_include_path(...) -} From 38b7b4405e83a1dc36a050458dbc652a4317afa0 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Sat, 30 Aug 2014 11:59:12 -0300 Subject: [PATCH 08/11] unittest: convert set.moon to lua --- test/unit/set.lua | 127 +++++++++++++++++++++++++++++++++++++++++++++ test/unit/set.moon | 72 ------------------------- 2 files changed, 127 insertions(+), 72 deletions(-) create mode 100644 test/unit/set.lua delete mode 100644 test/unit/set.moon diff --git a/test/unit/set.lua b/test/unit/set.lua new file mode 100644 index 00000000000000..bfb6b8c41c5c06 --- /dev/null +++ b/test/unit/set.lua @@ -0,0 +1,127 @@ +-- a set class for fast union/diff, can always return a table with the lines +-- in the same relative order in which they were added by calling the +-- to_table method. It does this by keeping two lua tables that mirror each +-- other: +-- 1) index => item +-- 2) item => index +local Set = {} + +function Set:new(items) + local obj = {} + setmetatable(obj, self) + self.__index = self + + if type(items) == 'table' then + local tempset = Set:new() + tempset:union_table(items) + obj.tbl = tempset:raw_tbl() + obj.items = tempset:raw_items() + obj.nelem = tempset:size() + else + obj.tbl = {} + obj.items = {} + obj.nelem = 0 + end + + return obj +end + +-- adds the argument Set to this Set +function Set:union(other) + for e in other:iterator() do + self:add(e) + end +end + +-- adds the argument table to this Set +function Set:union_table(t) + for k, v in pairs(t) do + self:add(v) + end +end + +-- subtracts the argument Set from this Set +function Set:diff(other) + if other:size() > self:size() then + -- this set is smaller than the other set + for e in self:iterator() do + if other:contains(e) then + self:remove(e) + end + end + else + -- this set is larger than the other set + for e in other:iterator() do + if self.items[e] then + self:remove(e) + end + end + end +end + +function Set:add(it) + if not self:contains(it) then + local idx = #self.tbl + 1 + self.tbl[idx] = it + self.items[it] = idx + self.nelem = self.nelem + 1 + end +end + +function Set:remove(it) + if self:contains(it) then + local idx = self.items[it] + self.tbl[idx] = nil + self.items[it] = nil + self.nelem = self.nelem - 1 + end +end + +function Set:contains(it) + return self.items[it] or false +end + +function Set:size() + return self.nelem +end + +function Set:raw_tbl() + return self.tbl +end + +function Set:raw_items() + return self.items +end + +function Set:iterator() + return pairs(self.items) +end + +function Set:to_table() + -- there might be gaps in @tbl, so we have to be careful and sort first + local keys + do + local _accum_0 = { } + local _len_0 = 1 + for idx, _ in pairs(self.tbl) do + _accum_0[_len_0] = idx + _len_0 = _len_0 + 1 + end + keys = _accum_0 + end + table.sort(keys) + local copy + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #keys do + local idx = keys[_index_0] + _accum_0[_len_0] = self.tbl[idx] + _len_0 = _len_0 + 1 + end + copy = _accum_0 + end + return copy +end + +return Set diff --git a/test/unit/set.moon b/test/unit/set.moon deleted file mode 100644 index affbd5c29aeaad..00000000000000 --- a/test/unit/set.moon +++ /dev/null @@ -1,72 +0,0 @@ --- a set class for fast union/diff, can always return a table with the lines --- in the same relative order in which they were added by calling the --- to_table method. It does this by keeping two lua tables that mirror each --- other: --- 1) index => item --- 2) item => index -class Set - new: (items) => - if type(items) == 'table' - tempset = Set() - tempset\union_table(items) - @tbl = tempset\raw_tbl! - @items = tempset\raw_items! - @nelem = tempset\size! - else - @tbl = {} - @items = {} - @nelem = 0 - - -- adds the argument Set to this Set - union: (other) => - for e in other\iterator! - @add(e) - - -- adds the argument table to this Set - union_table: (t) => - for k,v in pairs(t) - @add(v) - - -- subtracts the argument Set from this Set - diff: (other) => - if other\size! > @size! - -- this set is smaller than the other set - for e in @iterator! - if other\contains(e) - @remove(e) - else - -- this set is larger than the other set - for e in other\iterator! - if @items[e] - @remove(e) - - add: (it) => - if not @contains(it) - idx = #@tbl + 1 - @tbl[idx] = it - @items[it] = idx - @nelem += 1 - - remove: (it) => - if @contains(it) - idx = @items[it] - @tbl[idx] = nil - @items[it] = nil - @nelem -= 1 - - contains: (it) => - @items[it] or false - - size: => @nelem - raw_tbl: => @tbl - raw_items: => @items - iterator: => pairs(@items) - - to_table: => - -- there might be gaps in @tbl, so we have to be careful and sort first - keys = [idx for idx, _ in pairs(@tbl)] - table.sort(keys) - copy = [@tbl[idx] for idx in *keys] - copy - -return Set From 7b41fb383ab199ae6e7f3eb3d62a22da1a173e59 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Sat, 30 Aug 2014 23:49:56 -0300 Subject: [PATCH 09/11] unittest: Remove remaining moonscript references --- test/unit/formatc.lua | 1 - test/unit/garray_spec.lua | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/test/unit/formatc.lua b/test/unit/formatc.lua index dc1fb3fd9ccf28..bafaf093100e47 100644 --- a/test/unit/formatc.lua +++ b/test/unit/formatc.lua @@ -215,7 +215,6 @@ end -- uncomment the following lines (and comment the return) for standalone -- operation (very handy for debugging) local function standalone(...) - require "moonscript" Preprocess = require("preprocess") Preprocess.add_to_include_path('./../../src') Preprocess.add_to_include_path('./../../build/include') diff --git a/test/unit/garray_spec.lua b/test/unit/garray_spec.lua index ab38176c41672d..c0c6d7e9ce7322 100644 --- a/test/unit/garray_spec.lua +++ b/test/unit/garray_spec.lua @@ -13,11 +13,11 @@ local NULL = helpers.NULL local garray = cimport('./src/nvim/garray.h') -- define a basic interface to garray. We could make it a lot nicer by --- constructing a moonscript class wrapper around garray. It could for --- example associate ga_clear_strings to the underlying garray cdata if the --- garray is a string array. But for now I estimate that that kind of magic --- might make testing less "transparant" (i.e.: the interface would become --- quite different as to how one would use it from C. +-- constructing a class wrapper around garray. It could for example associate +-- ga_clear_strings to the underlying garray cdata if the garray is a string +-- array. But for now I estimate that that kind of magic might make testing +-- less "transparant" (i.e.: the interface would become quite different as to +-- how one would use it from C. -- accessors function ga_len(garr) From 80e421fe8d28cea6f0f6aabe030282f7526faddf Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Sun, 31 Aug 2014 00:02:26 -0300 Subject: [PATCH 10/11] build: Remove moonscript as a third-party build dependency --- third-party/CMakeLists.txt | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index 1935bff4ebfe6a..28a717bdce4840 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -150,31 +150,21 @@ if(USE_BUNDLED_LUAROCKS) add_dependencies(luarocks luajit) endif() - add_custom_command(OUTPUT ${DEPS_BIN_DIR}/moon ${DEPS_BIN_DIR}/moonc - COMMAND ${DEPS_BIN_DIR}/luarocks ARGS install moonscript - DEPENDS luarocks) - add_custom_target(moonscript - DEPENDS ${DEPS_BIN_DIR}/moon ${DEPS_BIN_DIR}/moonc) - - # Busted doesn't depend on luarocks, but luarocks is unhappy to have two - # instances running in parallel. So we depend on moonscript to force it - # to be serialized. add_custom_command(OUTPUT ${DEPS_BIN_DIR}/busted COMMAND ${DEPS_BIN_DIR}/luarocks ARGS install busted 1.10.0 - DEPENDS moonscript) + DEPENDS luarocks) add_custom_target(busted DEPENDS ${DEPS_BIN_DIR}/busted) - # Like busted dependency on moonscript, we add a dependency on busted - # to serialize the install + # lua-cmsgpack doesn't depend on busted, but luarocks is unhappy to have two + # instances running in parallel. So we depend on busted to force it + # to be serialized. add_custom_command(OUTPUT ${DEPS_LIB_DIR}/luarocks/rocks/lua-cmsgpack COMMAND ${DEPS_BIN_DIR}/luarocks ARGS install lua-cmsgpack DEPENDS busted) add_custom_target(lua-cmsgpack DEPENDS ${DEPS_LIB_DIR}/luarocks/rocks/lua-cmsgpack) - # lpeg is a moonscript dependency, but since it is also required for - # normal compilation(even without unit testing) we better add it explicitly. # Like before, depend on cmsgpack to ensure serialization of install commands add_custom_command(OUTPUT ${DEPS_LIB_DIR}/luarocks/rocks/lpeg COMMAND ${DEPS_BIN_DIR}/luarocks ARGS install lpeg @@ -182,7 +172,7 @@ if(USE_BUNDLED_LUAROCKS) add_custom_target(lpeg DEPENDS ${DEPS_LIB_DIR}/luarocks/rocks/lpeg) - list(APPEND THIRD_PARTY_DEPS moonscript busted lua-cmsgpack lpeg) + list(APPEND THIRD_PARTY_DEPS busted lua-cmsgpack lpeg) endif() add_custom_target(third-party ALL From c798611771a7cc184de52e0bcada0ceb60a93888 Mon Sep 17 00:00:00 2001 From: Stefan Hoffmann Date: Sun, 31 Aug 2014 08:45:54 -0300 Subject: [PATCH 11/11] unittest: Move FileInfo tests one level up --- test/unit/os/fs_spec.lua | 374 +++++++++++++++++++-------------------- 1 file changed, 187 insertions(+), 187 deletions(-) diff --git a/test/unit/os/fs_spec.lua b/test/unit/os/fs_spec.lua index 92e968abd72862..4cea8f55f10502 100644 --- a/test/unit/os/fs_spec.lua +++ b/test/unit/os/fs_spec.lua @@ -461,194 +461,194 @@ describe('fs function', function() eq(false, (os_isdir('unit-test-directory/new-dir'))) end) end) + end) + + describe('FileInfo', function() + function file_info_new() + local file_info = ffi.new('FileInfo[1]') + file_info[0].stat.st_ino = 0 + file_info[0].stat.st_dev = 0 + return file_info + end + + function is_file_info_filled(file_info) + return file_info[0].stat.st_ino > 0 and file_info[0].stat.st_dev > 0 + end + + function file_id_new() + local file_info = ffi.new('FileID[1]') + file_info[0].inode = 0 + file_info[0].device_id = 0 + return file_info + end + + describe('os_get_file_info', function() + it('returns false if given a non-existing file', function() + local file_info = file_info_new() + assert.is_false((fs.os_get_file_info('/non-existent', file_info))) + end) + + it('returns true if given an existing file and fills file_info', function() + local file_info = file_info_new() + local path = 'unit-test-directory/test.file' + assert.is_true((fs.os_get_file_info(path, file_info))) + assert.is_true((is_file_info_filled(file_info))) + end) + + it('returns the file info of the linked file, not the link', function() + local file_info = file_info_new() + local path = 'unit-test-directory/test_link.file' + assert.is_true((fs.os_get_file_info(path, file_info))) + assert.is_true((is_file_info_filled(file_info))) + local mode = tonumber(file_info[0].stat.st_mode) + return eq(ffi.C.kS_IFREG, (bit.band(mode, ffi.C.kS_IFMT))) + end) + end) + + describe('os_get_file_info_link', function() + it('returns false if given a non-existing file', function() + local file_info = file_info_new() + assert.is_false((fs.os_get_file_info_link('/non-existent', file_info))) + end) + + it('returns true if given an existing file and fills file_info', function() + local file_info = file_info_new() + local path = 'unit-test-directory/test.file' + assert.is_true((fs.os_get_file_info_link(path, file_info))) + assert.is_true((is_file_info_filled(file_info))) + end) + + it('returns the file info of the link, not the linked file', function() + local file_info = file_info_new() + local path = 'unit-test-directory/test_link.file' + assert.is_true((fs.os_get_file_info_link(path, file_info))) + assert.is_true((is_file_info_filled(file_info))) + local mode = tonumber(file_info[0].stat.st_mode) + eq(ffi.C.kS_IFLNK, (bit.band(mode, ffi.C.kS_IFMT))) + end) + end) + + describe('os_get_file_info_fd', function() + it('returns false if given an invalid file descriptor', function() + local file_info = file_info_new() + assert.is_false((fs.os_get_file_info_fd(-1, file_info))) + end) + + it('returns true if given a file descriptor and fills file_info', function() + local file_info = file_info_new() + local path = 'unit-test-directory/test.file' + local fd = ffi.C.open(path, 0) + assert.is_true((fs.os_get_file_info_fd(fd, file_info))) + assert.is_true((is_file_info_filled(file_info))) + ffi.C.close(fd) + end) + end) + + describe('os_file_info_id_equal', function() + it('returns false if file infos represent different files', function() + local file_info_1 = file_info_new() + local file_info_2 = file_info_new() + local path_1 = 'unit-test-directory/test.file' + local path_2 = 'unit-test-directory/test_2.file' + assert.is_true((fs.os_get_file_info(path_1, file_info_1))) + assert.is_true((fs.os_get_file_info(path_2, file_info_2))) + assert.is_false((fs.os_file_info_id_equal(file_info_1, file_info_2))) + end) + + it('returns true if file infos represent the same file', function() + local file_info_1 = file_info_new() + local file_info_2 = file_info_new() + local path = 'unit-test-directory/test.file' + assert.is_true((fs.os_get_file_info(path, file_info_1))) + assert.is_true((fs.os_get_file_info(path, file_info_2))) + assert.is_true((fs.os_file_info_id_equal(file_info_1, file_info_2))) + end) + + it('returns true if file infos represent the same file (symlink)', function() + local file_info_1 = file_info_new() + local file_info_2 = file_info_new() + local path_1 = 'unit-test-directory/test.file' + local path_2 = 'unit-test-directory/test_link.file' + assert.is_true((fs.os_get_file_info(path_1, file_info_1))) + assert.is_true((fs.os_get_file_info(path_2, file_info_2))) + assert.is_true((fs.os_file_info_id_equal(file_info_1, file_info_2))) + end) + end) + + describe('os_file_info_get_id', function() + it('extracts ino/dev from file_info into file_id', function() + local file_info = file_info_new() + local file_id = file_id_new() + local path = 'unit-test-directory/test.file' + assert.is_true((fs.os_get_file_info(path, file_info))) + fs.os_file_info_get_id(file_info, file_id) + eq(file_info[0].stat.st_ino, file_id[0].inode) + eq(file_info[0].stat.st_dev, file_id[0].device_id) + end) + end) + + describe('os_file_info_get_inode', function() + it('returns the inode from file_info', function() + local file_info = file_info_new() + local path = 'unit-test-directory/test.file' + assert.is_true((fs.os_get_file_info(path, file_info))) + local inode = fs.os_file_info_get_inode(file_info) + eq(file_info[0].stat.st_ino, inode) + end) + end) + + describe('os_get_file_id', function() + it('returns false if given an non-existing file', function() + local file_id = file_id_new() + assert.is_false((fs.os_get_file_id('/non-existent', file_id))) + end) + + it('returns true if given an existing file and fills file_id', function() + local file_id = file_id_new() + local path = 'unit-test-directory/test.file' + assert.is_true((fs.os_get_file_id(path, file_id))) + assert.is_true(0 < file_id[0].inode) + assert.is_true(0 < file_id[0].device_id) + end) + end) + + describe('os_file_id_equal', function() + it('returns true if two FileIDs are equal', function() + local file_id = file_id_new() + local path = 'unit-test-directory/test.file' + assert.is_true((fs.os_get_file_id(path, file_id))) + assert.is_true((fs.os_file_id_equal(file_id, file_id))) + end) + + it('returns false if two FileIDs are not equal', function() + local file_id_1 = file_id_new() + local file_id_2 = file_id_new() + local path_1 = 'unit-test-directory/test.file' + local path_2 = 'unit-test-directory/test_2.file' + assert.is_true((fs.os_get_file_id(path_1, file_id_1))) + assert.is_true((fs.os_get_file_id(path_2, file_id_2))) + assert.is_false((fs.os_file_id_equal(file_id_1, file_id_2))) + end) + end) - describe('FileInfo', function() - function file_info_new() - local file_info = ffi.new('FileInfo[1]') - file_info[0].stat.st_ino = 0 - file_info[0].stat.st_dev = 0 - return file_info - end - - function is_file_info_filled(file_info) - return file_info[0].stat.st_ino > 0 and file_info[0].stat.st_dev > 0 - end - - function file_id_new() - local file_info = ffi.new('FileID[1]') - file_info[0].inode = 0 - file_info[0].device_id = 0 - return file_info - end - - describe('os_get_file_info', function() - it('returns false if given a non-existing file', function() - local file_info = file_info_new() - assert.is_false((fs.os_get_file_info('/non-existent', file_info))) - end) - - it('returns true if given an existing file and fills file_info', function() - local file_info = file_info_new() - local path = 'unit-test-directory/test.file' - assert.is_true((fs.os_get_file_info(path, file_info))) - assert.is_true((is_file_info_filled(file_info))) - end) - - it('returns the file info of the linked file, not the link', function() - local file_info = file_info_new() - local path = 'unit-test-directory/test_link.file' - assert.is_true((fs.os_get_file_info(path, file_info))) - assert.is_true((is_file_info_filled(file_info))) - local mode = tonumber(file_info[0].stat.st_mode) - return eq(ffi.C.kS_IFREG, (bit.band(mode, ffi.C.kS_IFMT))) - end) - end) - - describe('os_get_file_info_link', function() - it('returns false if given a non-existing file', function() - local file_info = file_info_new() - assert.is_false((fs.os_get_file_info_link('/non-existent', file_info))) - end) - - it('returns true if given an existing file and fills file_info', function() - local file_info = file_info_new() - local path = 'unit-test-directory/test.file' - assert.is_true((fs.os_get_file_info_link(path, file_info))) - assert.is_true((is_file_info_filled(file_info))) - end) - - it('returns the file info of the link, not the linked file', function() - local file_info = file_info_new() - local path = 'unit-test-directory/test_link.file' - assert.is_true((fs.os_get_file_info_link(path, file_info))) - assert.is_true((is_file_info_filled(file_info))) - local mode = tonumber(file_info[0].stat.st_mode) - eq(ffi.C.kS_IFLNK, (bit.band(mode, ffi.C.kS_IFMT))) - end) - end) - - describe('os_get_file_info_fd', function() - it('returns false if given an invalid file descriptor', function() - local file_info = file_info_new() - assert.is_false((fs.os_get_file_info_fd(-1, file_info))) - end) - - it('returns true if given a file descriptor and fills file_info', function() - local file_info = file_info_new() - local path = 'unit-test-directory/test.file' - local fd = ffi.C.open(path, 0) - assert.is_true((fs.os_get_file_info_fd(fd, file_info))) - assert.is_true((is_file_info_filled(file_info))) - ffi.C.close(fd) - end) - end) - - describe('os_file_info_id_equal', function() - it('returns false if file infos represent different files', function() - local file_info_1 = file_info_new() - local file_info_2 = file_info_new() - local path_1 = 'unit-test-directory/test.file' - local path_2 = 'unit-test-directory/test_2.file' - assert.is_true((fs.os_get_file_info(path_1, file_info_1))) - assert.is_true((fs.os_get_file_info(path_2, file_info_2))) - assert.is_false((fs.os_file_info_id_equal(file_info_1, file_info_2))) - end) - - it('returns true if file infos represent the same file', function() - local file_info_1 = file_info_new() - local file_info_2 = file_info_new() - local path = 'unit-test-directory/test.file' - assert.is_true((fs.os_get_file_info(path, file_info_1))) - assert.is_true((fs.os_get_file_info(path, file_info_2))) - assert.is_true((fs.os_file_info_id_equal(file_info_1, file_info_2))) - end) - - it('returns true if file infos represent the same file (symlink)', function() - local file_info_1 = file_info_new() - local file_info_2 = file_info_new() - local path_1 = 'unit-test-directory/test.file' - local path_2 = 'unit-test-directory/test_link.file' - assert.is_true((fs.os_get_file_info(path_1, file_info_1))) - assert.is_true((fs.os_get_file_info(path_2, file_info_2))) - assert.is_true((fs.os_file_info_id_equal(file_info_1, file_info_2))) - end) - end) - - describe('os_file_info_get_id', function() - it('extracts ino/dev from file_info into file_id', function() - local file_info = file_info_new() - local file_id = file_id_new() - local path = 'unit-test-directory/test.file' - assert.is_true((fs.os_get_file_info(path, file_info))) - fs.os_file_info_get_id(file_info, file_id) - eq(file_info[0].stat.st_ino, file_id[0].inode) - eq(file_info[0].stat.st_dev, file_id[0].device_id) - end) - end) - - describe('os_file_info_get_inode', function() - it('returns the inode from file_info', function() - local file_info = file_info_new() - local path = 'unit-test-directory/test.file' - assert.is_true((fs.os_get_file_info(path, file_info))) - local inode = fs.os_file_info_get_inode(file_info) - eq(file_info[0].stat.st_ino, inode) - end) - end) - - describe('os_get_file_id', function() - it('returns false if given an non-existing file', function() - local file_id = file_id_new() - assert.is_false((fs.os_get_file_id('/non-existent', file_id))) - end) - - it('returns true if given an existing file and fills file_id', function() - local file_id = file_id_new() - local path = 'unit-test-directory/test.file' - assert.is_true((fs.os_get_file_id(path, file_id))) - assert.is_true(0 < file_id[0].inode) - assert.is_true(0 < file_id[0].device_id) - end) - end) - - describe('os_file_id_equal', function() - it('returns true if two FileIDs are equal', function() - local file_id = file_id_new() - local path = 'unit-test-directory/test.file' - assert.is_true((fs.os_get_file_id(path, file_id))) - assert.is_true((fs.os_file_id_equal(file_id, file_id))) - end) - - it('returns false if two FileIDs are not equal', function() - local file_id_1 = file_id_new() - local file_id_2 = file_id_new() - local path_1 = 'unit-test-directory/test.file' - local path_2 = 'unit-test-directory/test_2.file' - assert.is_true((fs.os_get_file_id(path_1, file_id_1))) - assert.is_true((fs.os_get_file_id(path_2, file_id_2))) - assert.is_false((fs.os_file_id_equal(file_id_1, file_id_2))) - end) - end) - - describe('os_file_id_equal_file_info', function() - it('returns true if file_id and file_info represent the same file', function() - local file_id = file_id_new() - local file_info = file_info_new() - local path = 'unit-test-directory/test.file' - assert.is_true((fs.os_get_file_id(path, file_id))) - assert.is_true((fs.os_get_file_info(path, file_info))) - assert.is_true((fs.os_file_id_equal_file_info(file_id, file_info))) - end) - - it('returns false if file_id and file_info represent different files', function() - local file_id = file_id_new() - local file_info = file_info_new() - local path_1 = 'unit-test-directory/test.file' - local path_2 = 'unit-test-directory/test_2.file' - assert.is_true((fs.os_get_file_id(path_1, file_id))) - assert.is_true((fs.os_get_file_info(path_2, file_info))) - assert.is_false((fs.os_file_id_equal_file_info(file_id, file_info))) - end) + describe('os_file_id_equal_file_info', function() + it('returns true if file_id and file_info represent the same file', function() + local file_id = file_id_new() + local file_info = file_info_new() + local path = 'unit-test-directory/test.file' + assert.is_true((fs.os_get_file_id(path, file_id))) + assert.is_true((fs.os_get_file_info(path, file_info))) + assert.is_true((fs.os_file_id_equal_file_info(file_id, file_info))) + end) + + it('returns false if file_id and file_info represent different files', function() + local file_id = file_id_new() + local file_info = file_info_new() + local path_1 = 'unit-test-directory/test.file' + local path_2 = 'unit-test-directory/test_2.file' + assert.is_true((fs.os_get_file_id(path_1, file_id))) + assert.is_true((fs.os_get_file_info(path_2, file_info))) + assert.is_false((fs.os_file_id_equal_file_info(file_id, file_info))) end) end) end)