Merge #8276 'startup: Make -s - read from stdin'

This commit is contained in:
Justin M. Keyes 2018-04-17 10:33:36 +02:00 committed by GitHub
commit 7a13611ba2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 398 additions and 152 deletions

View File

@ -48,8 +48,14 @@
#include "nvim/event/loop.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/os/fileio.h"
#include "nvim/api/private/handle.h"
/// Index in scriptin
static int curscript = 0;
FileDescriptor *scriptin[NSCRIPT] = { NULL };
/*
* These buffers are used for storing:
* - stuffed characters: A command that is translated into another command.
@ -1243,10 +1249,13 @@ openscript (
++curscript;
/* use NameBuff for expanded name */
expand_env(name, NameBuff, MAXPATHL);
if ((scriptin[curscript] = mch_fopen((char *)NameBuff, READBIN)) == NULL) {
EMSG2(_(e_notopen), name);
if (curscript)
--curscript;
int error;
if ((scriptin[curscript] = file_open_new(&error, (char *)NameBuff,
kFileReadOnly, 0)) == NULL) {
emsgf(_(e_notopen_2), name, os_strerror(error));
if (curscript) {
curscript--;
}
return;
}
save_typebuf();
@ -1296,7 +1305,7 @@ static void closescript(void)
free_typebuf();
typebuf = saved_typebuf[curscript];
fclose(scriptin[curscript]);
file_free(scriptin[curscript], false);
scriptin[curscript] = NULL;
if (curscript > 0)
--curscript;
@ -2336,9 +2345,8 @@ inchar (
int tb_change_cnt
)
{
int len = 0; /* init for GCC */
int retesc = FALSE; /* return ESC with gotint */
int script_char;
int len = 0; // Init for GCC.
int retesc = false; // Return ESC with gotint.
if (wait_time == -1L || wait_time > 100L) {
// flush output before waiting
@ -2356,45 +2364,38 @@ inchar (
}
undo_off = FALSE; /* restart undo now */
/*
* Get a character from a script file if there is one.
* If interrupted: Stop reading script files, close them all.
*/
script_char = -1;
while (scriptin[curscript] != NULL && script_char < 0
&& !ignore_script
) {
if (got_int || (script_char = getc(scriptin[curscript])) < 0) {
/* Reached EOF.
* Careful: closescript() frees typebuf.tb_buf[] and buf[] may
* point inside typebuf.tb_buf[]. Don't use buf[] after this! */
// Get a character from a script file if there is one.
// If interrupted: Stop reading script files, close them all.
ptrdiff_t read_size = -1;
while (scriptin[curscript] != NULL && read_size <= 0 && !ignore_script) {
char script_char;
if (got_int
|| (read_size = file_read(scriptin[curscript], &script_char, 1)) != 1) {
// Reached EOF or some error occurred.
// Careful: closescript() frees typebuf.tb_buf[] and buf[] may
// point inside typebuf.tb_buf[]. Don't use buf[] after this!
closescript();
/*
* When reading script file is interrupted, return an ESC to get
* back to normal mode.
* Otherwise return -1, because typebuf.tb_buf[] has changed.
*/
if (got_int)
retesc = TRUE;
else
// When reading script file is interrupted, return an ESC to get
// back to normal mode.
// Otherwise return -1, because typebuf.tb_buf[] has changed.
if (got_int) {
retesc = true;
} else {
return -1;
}
} else {
buf[0] = (char_u)script_char;
len = 1;
}
}
if (script_char < 0) { /* did not get a character from script */
/*
* If we got an interrupt, skip all previously typed characters and
* return TRUE if quit reading script file.
* Stop reading typeahead when a single CTRL-C was read,
* fill_input_buf() returns this when not able to read from stdin.
* Don't use buf[] here, closescript() may have freed typebuf.tb_buf[]
* and buf may be pointing inside typebuf.tb_buf[].
*/
if (read_size <= 0) { // Did not get a character from script.
// If we got an interrupt, skip all previously typed characters and
// return TRUE if quit reading script file.
// Stop reading typeahead when a single CTRL-C was read,
// fill_input_buf() returns this when not able to read from stdin.
// Don't use buf[] here, closescript() may have freed typebuf.tb_buf[]
// and buf may be pointing inside typebuf.tb_buf[].
if (got_int) {
#define DUM_LEN MAXMAPLEN * 3 + 3
char_u dum[DUM_LEN + 1];
@ -2407,21 +2408,18 @@ inchar (
return retesc;
}
/*
* Always flush the output characters when getting input characters
* from the user.
*/
// Always flush the output characters when getting input characters
// from the user.
ui_flush();
/*
* Fill up to a third of the buffer, because each character may be
* tripled below.
*/
// Fill up to a third of the buffer, because each character may be
// tripled below.
len = os_inchar(buf, maxlen / 3, (int)wait_time, tb_change_cnt);
}
if (typebuf_changed(tb_change_cnt))
if (typebuf_changed(tb_change_cnt)) {
return 0;
}
return fix_input_buffer(buf, len);
}

View File

@ -1,24 +1,30 @@
#ifndef NVIM_GETCHAR_H
#define NVIM_GETCHAR_H
#include "nvim/os/fileio.h"
#include "nvim/types.h"
#include "nvim/buffer_defs.h"
#include "nvim/ex_cmds_defs.h"
/// Values for "noremap" argument of ins_typebuf(). Also used for
/// map->m_noremap and menu->noremap[].
/// @addtogroup REMAP_VALUES
/// @{
#define REMAP_YES 0 ///< allow remapping
#define REMAP_NONE -1 ///< no remapping
#define REMAP_SCRIPT -2 ///< remap script-local mappings only
#define REMAP_SKIP -3 ///< no remapping for first char
/// @}
/// Values for "noremap" argument of ins_typebuf()
///
/// Also used for map->m_noremap and menu->noremap[].
enum {
REMAP_YES = 0, ///< Allow remapping.
REMAP_NONE = -1, ///< No remapping.
REMAP_SCRIPT = -2, ///< Remap script-local mappings only.
REMAP_SKIP = -3, ///< No remapping for first char.
} RemapValues;
#define KEYLEN_PART_KEY -1 /* keylen value for incomplete key-code */
#define KEYLEN_PART_MAP -2 /* keylen value for incomplete mapping */
#define KEYLEN_REMOVED 9999 /* keylen value for removed sequence */
/// Maximum number of streams to read script from
enum { NSCRIPT = 15 };
/// Streams to read script from
extern FileDescriptor *scriptin[NSCRIPT];
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "getchar.h.generated.h"

View File

@ -834,10 +834,7 @@ EXTERN int do_redraw INIT(= FALSE); /* extra redraw once */
EXTERN int need_highlight_changed INIT(= true);
EXTERN char *used_shada_file INIT(= NULL); // name of the ShaDa file to use
#define NSCRIPT 15
EXTERN FILE *scriptin[NSCRIPT]; /* streams to read script from */
EXTERN int curscript INIT(= 0); /* index in scriptin[] */
EXTERN FILE *scriptout INIT(= NULL); /* stream to write script to */
EXTERN FILE *scriptout INIT(= NULL); ///< Stream to write script to.
// volatile because it is used in a signal handler.
EXTERN volatile int got_int INIT(= false); // set to true when interrupt
@ -1085,6 +1082,7 @@ EXTERN char_u e_norange[] INIT(= N_("E481: No range allowed"));
EXTERN char_u e_noroom[] INIT(= N_("E36: Not enough room"));
EXTERN char_u e_notmp[] INIT(= N_("E483: Can't get temp file name"));
EXTERN char_u e_notopen[] INIT(= N_("E484: Can't open file %s"));
EXTERN char_u e_notopen_2[] INIT(= N_("E484: Can't open file %s: %s"));
EXTERN char_u e_notread[] INIT(= N_("E485: Can't read file %s"));
EXTERN char_u e_nowrtmsg[] INIT(= N_(
"E37: No write since last change (add ! to override)"));

View File

@ -787,7 +787,8 @@ static void command_line_scan(mparm_T *parmp)
mch_exit(0);
} else if (STRICMP(argv[0] + argv_idx, "api-info") == 0) {
FileDescriptor fp;
const int fof_ret = file_open_fd(&fp, STDOUT_FILENO, true);
const int fof_ret = file_open_fd(&fp, STDOUT_FILENO,
kFileWriteOnly);
msgpack_packer *p = msgpack_packer_new(&fp, msgpack_file_write);
if (fof_ret != 0) {
@ -1085,17 +1086,36 @@ static void command_line_scan(mparm_T *parmp)
case 's': /* "-s {scriptin}" read from script file */
if (scriptin[0] != NULL) {
scripterror:
mch_errmsg(_("Attempt to open script file again: \""));
mch_errmsg(argv[-1]);
mch_errmsg(" ");
mch_errmsg(argv[0]);
mch_errmsg("\"\n");
vim_snprintf((char *)IObuff, IOSIZE,
_("Attempt to open script file again: \"%s %s\"\n"),
argv[-1], argv[0]);
mch_errmsg((const char *)IObuff);
mch_exit(2);
}
if ((scriptin[0] = mch_fopen(argv[0], READBIN)) == NULL) {
mch_errmsg(_("Cannot open for reading: \""));
mch_errmsg(argv[0]);
mch_errmsg("\"\n");
int error;
if (STRCMP(argv[0], "-") == 0) {
const int stdin_dup_fd = os_dup(STDIN_FILENO);
#ifdef WIN32
// On Windows, replace the original stdin with the
// console input handle.
close(STDIN_FILENO);
const HANDLE conin_handle =
CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ, (LPSECURITY_ATTRIBUTES)NULL,
OPEN_EXISTING, 0, (HANDLE)NULL);
const int conin_fd = _open_osfhandle(conin_handle, _O_RDONLY);
assert(conin_fd == STDIN_FILENO);
#endif
FileDescriptor *const stdin_dup = file_open_fd_new(
&error, stdin_dup_fd, kFileReadOnly|kFileNonBlocking);
assert(stdin_dup != NULL);
scriptin[0] = stdin_dup;
} else if ((scriptin[0] = file_open_new(
&error, argv[0], kFileReadOnly|kFileNonBlocking, 0)) == NULL) {
vim_snprintf((char *)IObuff, IOSIZE,
_("Cannot open for reading: \"%s\": %s\n"),
argv[0], os_strerror(error));
mch_errmsg((const char *)IObuff);
mch_exit(2);
}
save_typebuf();

View File

@ -2351,10 +2351,9 @@ static int do_more_prompt(int typed_char)
* yet. When stderr can't be used, collect error messages until the GUI has
* started and they can be displayed in a message box.
*/
void mch_errmsg(char *str)
void mch_errmsg(const char *const str)
FUNC_ATTR_NONNULL_ALL
{
int len;
#ifdef UNIX
/* On Unix use stderr if it's a tty.
* When not going to start the GUI also use stderr.
@ -2368,14 +2367,13 @@ void mch_errmsg(char *str)
/* avoid a delay for a message that isn't there */
emsg_on_display = FALSE;
len = (int)STRLEN(str) + 1;
const size_t len = strlen(str) + 1;
if (error_ga.ga_data == NULL) {
ga_set_growsize(&error_ga, 80);
error_ga.ga_itemsize = 1;
}
ga_grow(&error_ga, len);
memmove((char_u *)error_ga.ga_data + error_ga.ga_len,
(char_u *)str, len);
memmove(error_ga.ga_data + error_ga.ga_len, str, len);
#ifdef UNIX
/* remove CR characters, they are displayed */
{

View File

@ -83,7 +83,7 @@ int file_open(FileDescriptor *const ret_fp, const char *const fname,
if (fd < 0) {
return fd;
}
return file_open_fd(ret_fp, fd, (wr == kTrue));
return file_open_fd(ret_fp, fd, flags);
}
/// Wrap file descriptor with FileDescriptor structure
@ -94,14 +94,23 @@ int file_open(FileDescriptor *const ret_fp, const char *const fname,
/// @param[out] ret_fp Address where information needed for reading from or
/// writing to a file is saved
/// @param[in] fd File descriptor to wrap.
/// @param[in] wr True if fd is opened for writing only, false if it is read
/// only.
/// @param[in] flags Flags, @see FileOpenFlags. Currently reading from and
/// writing to the file at once is not supported, so either
/// FILE_WRITE_ONLY or FILE_READ_ONLY is required.
///
/// @return Error code (@see os_strerror()) or 0. Currently always returns 0.
int file_open_fd(FileDescriptor *const ret_fp, const int fd, const bool wr)
int file_open_fd(FileDescriptor *const ret_fp, const int fd,
const int flags)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
ret_fp->wr = wr;
ret_fp->wr = !!(flags & (kFileCreate
|kFileCreateOnly
|kFileTruncate
|kFileAppend
|kFileWriteOnly));
ret_fp->non_blocking = !!(flags & kFileNonBlocking);
// Non-blocking writes not supported currently.
assert(!ret_fp->wr || !ret_fp->non_blocking);
ret_fp->fd = fd;
ret_fp->eof = false;
ret_fp->rv = rbuffer_new(kRWBufferSize);
@ -138,15 +147,17 @@ FileDescriptor *file_open_new(int *const error, const char *const fname,
///
/// @param[out] error Error code, or 0 on success. @see os_strerror()
/// @param[in] fd File descriptor to wrap.
/// @param[in] wr True if fd is opened for writing only, false if it is read
/// only.
/// @param[in] flags Flags, @see FileOpenFlags.
/// @param[in] mode Permissions for the newly created file (ignored if flags
/// does not have FILE_CREATE\*).
///
/// @return [allocated] Opened file or NULL in case of error.
FileDescriptor *file_open_fd_new(int *const error, const int fd, const bool wr)
FileDescriptor *file_open_fd_new(int *const error, const int fd,
const int flags)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT
{
FileDescriptor *const fp = xmalloc(sizeof(*fp));
if ((*error = file_open_fd(fp, fd, wr)) != 0) {
if ((*error = file_open_fd(fp, fd, flags)) != 0) {
xfree(fp);
return NULL;
}
@ -244,7 +255,8 @@ static void file_rb_write_full_cb(RBuffer *const rv, FileDescriptor *const fp)
return;
}
const size_t read_bytes = rbuffer_read(rv, writebuf, kRWBufferSize);
const ptrdiff_t wres = os_write(fp->fd, writebuf, read_bytes);
const ptrdiff_t wres = os_write(fp->fd, writebuf, read_bytes,
fp->non_blocking);
if (wres != (ptrdiff_t)read_bytes) {
if (wres >= 0) {
fp->_error = UV_EIO;
@ -270,6 +282,7 @@ ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf,
char *buf = ret_buf;
size_t read_remaining = size;
RBuffer *const rv = fp->rv;
bool called_read = false;
while (read_remaining) {
const size_t rv_size = rbuffer_size(rv);
if (rv_size > 0) {
@ -277,7 +290,9 @@ ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf,
buf += rsize;
read_remaining -= rsize;
}
if (fp->eof) {
if (fp->eof
// Allow only at most one os_read[v] call.
|| (called_read && fp->non_blocking)) {
break;
}
if (read_remaining) {
@ -294,7 +309,7 @@ ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf,
};
assert(write_count == kRWBufferSize);
const ptrdiff_t r_ret = os_readv(fp->fd, &fp->eof, iov,
ARRAY_SIZE(iov));
ARRAY_SIZE(iov), fp->non_blocking);
if (r_ret > 0) {
if (r_ret > (ptrdiff_t)read_remaining) {
rbuffer_produced(rv, (size_t)(r_ret - (ptrdiff_t)read_remaining));
@ -310,7 +325,8 @@ ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf,
if (read_remaining >= kRWBufferSize) {
// …otherwise leave RBuffer empty and populate only target buffer,
// because filtering information through rbuffer will be more syscalls.
const ptrdiff_t r_ret = os_read(fp->fd, &fp->eof, buf, read_remaining);
const ptrdiff_t r_ret = os_read(fp->fd, &fp->eof, buf, read_remaining,
fp->non_blocking);
if (r_ret >= 0) {
read_remaining -= (size_t)r_ret;
return (ptrdiff_t)(size - read_remaining);
@ -321,7 +337,7 @@ ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf,
size_t write_count;
const ptrdiff_t r_ret = os_read(fp->fd, &fp->eof,
rbuffer_write_ptr(rv, &write_count),
kRWBufferSize);
kRWBufferSize, fp->non_blocking);
assert(write_count == kRWBufferSize);
if (r_ret > 0) {
rbuffer_produced(rv, (size_t)r_ret);
@ -330,6 +346,7 @@ ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf,
}
}
#endif
called_read = true;
}
}
return (ptrdiff_t)(size - read_remaining);

View File

@ -14,6 +14,7 @@ typedef struct {
RBuffer *rv; ///< Read or write buffer.
bool wr; ///< True if file is in write mode.
bool eof; ///< True if end of file was encountered.
bool non_blocking; ///< True if EAGAIN should not restart syscalls.
} FileDescriptor;
/// file_open() flags
@ -32,6 +33,8 @@ typedef enum {
///< kFileCreateOnly.
kFileAppend = 64, ///< Append to the file. Implies kFileWriteOnly. Cannot
///< be used with kFileCreateOnly.
kFileNonBlocking = 128, ///< Do not restart read() or write() syscall if
///< EAGAIN was encountered.
} FileOpenFlags;
static inline bool file_eof(const FileDescriptor *const fp)

View File

@ -436,6 +436,29 @@ int os_close(const int fd)
return r;
}
/// Duplicate file descriptor
///
/// @param[in] fd File descriptor to duplicate.
///
/// @return New file descriptor or libuv error code (< 0).
int os_dup(const int fd)
FUNC_ATTR_WARN_UNUSED_RESULT
{
int ret;
os_dup_dup:
ret = dup(fd);
if (ret < 0) {
const int error = os_translate_sys_error(errno);
errno = 0;
if (error == UV_EINTR) {
goto os_dup_dup;
} else {
return error;
}
}
return ret;
}
/// Read from a file
///
/// Handles EINTR and ENOMEM, but not other errors.
@ -445,10 +468,11 @@ int os_close(const int fd)
/// to false. Initial value is ignored.
/// @param[out] ret_buf Buffer to write to. May be NULL if size is zero.
/// @param[in] size Amount of bytes to read.
/// @param[in] non_blocking Do not restart syscall if EAGAIN was encountered.
///
/// @return Number of bytes read or libuv error code (< 0).
ptrdiff_t os_read(const int fd, bool *ret_eof, char *const ret_buf,
const size_t size)
ptrdiff_t os_read(const int fd, bool *const ret_eof, char *const ret_buf,
const size_t size, const bool non_blocking)
FUNC_ATTR_WARN_UNUSED_RESULT
{
*ret_eof = false;
@ -468,7 +492,9 @@ ptrdiff_t os_read(const int fd, bool *ret_eof, char *const ret_buf,
if (cur_read_bytes < 0) {
const int error = os_translate_sys_error(errno);
errno = 0;
if (error == UV_EINTR || error == UV_EAGAIN) {
if (non_blocking && error == UV_EAGAIN) {
break;
} else if (error == UV_EINTR || error == UV_EAGAIN) {
continue;
} else if (error == UV_ENOMEM && !did_try_to_free) {
try_to_free_memory();
@ -498,7 +524,11 @@ ptrdiff_t os_read(const int fd, bool *ret_eof, char *const ret_buf,
/// may change, it is incorrect to use data it points to after
/// os_readv().
/// @param[in] iov_size Number of buffers in iov.
ptrdiff_t os_readv(int fd, bool *ret_eof, struct iovec *iov, size_t iov_size)
/// @param[in] non_blocking Do not restart syscall if EAGAIN was encountered.
///
/// @return Number of bytes read or libuv error code (< 0).
ptrdiff_t os_readv(const int fd, bool *const ret_eof, struct iovec *iov,
size_t iov_size, const bool non_blocking)
FUNC_ATTR_NONNULL_ALL
{
*ret_eof = false;
@ -531,7 +561,9 @@ ptrdiff_t os_readv(int fd, bool *ret_eof, struct iovec *iov, size_t iov_size)
} else if (cur_read_bytes < 0) {
const int error = os_translate_sys_error(errno);
errno = 0;
if (error == UV_EINTR || error == UV_EAGAIN) {
if (non_blocking && error == UV_EAGAIN) {
break;
} else if (error == UV_EINTR || error == UV_EAGAIN) {
continue;
} else if (error == UV_ENOMEM && !did_try_to_free) {
try_to_free_memory();
@ -551,9 +583,11 @@ ptrdiff_t os_readv(int fd, bool *ret_eof, struct iovec *iov, size_t iov_size)
/// @param[in] fd File descriptor to write to.
/// @param[in] buf Data to write. May be NULL if size is zero.
/// @param[in] size Amount of bytes to write.
/// @param[in] non_blocking Do not restart syscall if EAGAIN was encountered.
///
/// @return Number of bytes written or libuv error code (< 0).
ptrdiff_t os_write(const int fd, const char *const buf, const size_t size)
ptrdiff_t os_write(const int fd, const char *const buf, const size_t size,
const bool non_blocking)
FUNC_ATTR_WARN_UNUSED_RESULT
{
if (buf == NULL) {
@ -571,7 +605,9 @@ ptrdiff_t os_write(const int fd, const char *const buf, const size_t size)
if (cur_written_bytes < 0) {
const int error = os_translate_sys_error(errno);
errno = 0;
if (error == UV_EINTR || error == UV_EAGAIN) {
if (non_blocking && error == UV_EAGAIN) {
break;
} else if (error == UV_EINTR || error == UV_EAGAIN) {
continue;
} else {
return error;

View File

@ -0,0 +1,131 @@
local lfs = require('lfs')
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local eq = helpers.eq
local feed = helpers.feed
local eval = helpers.eval
local clear = helpers.clear
local funcs = helpers.funcs
local nvim_prog = helpers.nvim_prog
local write_file = helpers.write_file
local function nvim_prog_abs()
-- system(['build/bin/nvim']) does not work for whatever reason. It needs to
-- either be executable searched in $PATH or something starting with / or ./.
if nvim_prog:match('[/\\]') then
return funcs.fnamemodify(nvim_prog, ':p')
else
return nvim_prog
end
end
describe('Command-line option', function()
describe('-s', function()
local fname = 'Xtest-functional-core-main-s'
local fname_2 = fname .. '.2'
local nonexistent_fname = fname .. '.nonexistent'
local dollar_fname = '$' .. fname
before_each(function()
clear()
os.remove(fname)
os.remove(dollar_fname)
end)
after_each(function()
os.remove(fname)
os.remove(dollar_fname)
end)
it('treats - as stdin', function()
eq(nil, lfs.attributes(fname))
funcs.system(
{nvim_prog_abs(), '-u', 'NONE', '-i', 'NONE', '--headless',
'--cmd', 'set noswapfile shortmess+=IFW fileformats=unix',
'-s', '-', fname},
{':call setline(1, "42")', ':wqall!', ''})
eq(0, eval('v:shell_error'))
local attrs = lfs.attributes(fname)
eq(#('42\n'), attrs.size)
end)
it('does not expand $VAR', function()
eq(nil, lfs.attributes(fname))
eq(true, not not dollar_fname:find('%$%w+'))
write_file(dollar_fname, ':call setline(1, "100500")\n:wqall!\n')
funcs.system(
{nvim_prog_abs(), '-u', 'NONE', '-i', 'NONE', '--headless',
'--cmd', 'set noswapfile shortmess+=IFW fileformats=unix',
'-s', dollar_fname, fname})
eq(0, eval('v:shell_error'))
local attrs = lfs.attributes(fname)
eq(#('100500\n'), attrs.size)
end)
it('does not crash after reading from stdin in non-headless mode', function()
if helpers.pending_win32(pending) then return end
local screen = Screen.new(40, 8)
screen:attach()
funcs.termopen({
nvim_prog_abs(), '-u', 'NONE', '-i', 'NONE',
'--cmd', 'set noswapfile shortmess+=IFW fileformats=unix',
'-s', '-'
})
screen:expect([[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{2:[No Name] 0,0-1 All}|
|
|
]], {
[1] = {foreground = 4210943, special = Screen.colors.Grey0},
[2] = {special = Screen.colors.Grey0, bold = true, reverse = true}
})
feed('i:cq<CR><C-\\><C-n>')
screen:expect([[
^ |
[Process exited 1] |
|
|
|
|
|
|
]])
--[=[ Example of incorrect output:
screen:expect([[
^nvim: /var/tmp/portage/dev-libs/libuv-1.|
10.2/work/libuv-1.10.2/src/unix/core.c:5|
19: uv__close: Assertion `fd > STDERR_FI|
LENO' failed. |
|
[Process exited 6] |
|
|
]])
]=]
end)
it('errors out when trying to use nonexistent file with -s', function()
eq(
'Cannot open for reading: "'..nonexistent_fname..'": no such file or directory\n',
funcs.system(
{nvim_prog_abs(), '-u', 'NONE', '-i', 'NONE', '--headless',
'--cmd', 'set noswapfile shortmess+=IFW fileformats=unix',
'--cmd', 'language C',
'-s', nonexistent_fname}))
eq(2, eval('v:shell_error'))
end)
it('errors out when trying to use -s twice', function()
write_file(fname, ':call setline(1, "1")\n:wqall!\n')
write_file(dollar_fname, ':call setline(1, "2")\n:wqall!\n')
eq(
'Attempt to open script file again: "-s '..dollar_fname..'"\n',
funcs.system(
{nvim_prog_abs(), '-u', 'NONE', '-i', 'NONE', '--headless',
'--cmd', 'set noswapfile shortmess+=IFW fileformats=unix',
'--cmd', 'language C',
'-s', fname, '-s', dollar_fname, fname_2}))
eq(2, eval('v:shell_error'))
eq(nil, lfs.attributes(fname_2))
end)
end)
end)

View File

@ -1,6 +1,38 @@
local assert = require('luassert')
local lfs = require('lfs')
local quote_me = '[^.%w%+%-%@%_%/]' -- complement (needn't quote)
local function shell_quote(str)
if string.find(str, quote_me) or str == '' then
return '"' .. str:gsub('[$%%"\\]', '\\%0') .. '"'
else
return str
end
end
local function argss_to_cmd(...)
local cmd = ''
for i = 1, select('#', ...) do
local arg = select(i, ...)
if type(arg) == 'string' then
cmd = cmd .. ' ' ..shell_quote(arg)
else
for _, subarg in ipairs(arg) do
cmd = cmd .. ' ' .. shell_quote(subarg)
end
end
end
return cmd
end
local function popen_r(...)
return io.popen(argss_to_cmd(...), 'r')
end
local function popen_w(...)
return io.popen(argss_to_cmd(...), 'w')
end
local check_logs_useless_lines = {
['Warning: noted but unhandled ioctl']=1,
['could cause spurious value errors to appear']=2,
@ -121,7 +153,7 @@ local uname = (function()
return platform
end
local status, f = pcall(io.popen, "uname -s")
local status, f = pcall(popen_r, 'uname', '-s')
if status then
platform = f:read("*l")
f:close()
@ -253,7 +285,7 @@ local function check_cores(app, force)
end
local function which(exe)
local pipe = io.popen('which ' .. exe, 'r')
local pipe = popen_r('which', exe)
local ret = pipe:read('*a')
pipe:close()
if ret == '' then
@ -263,6 +295,19 @@ local function which(exe)
end
end
local function repeated_read_cmd(...)
for _ = 1, 10 do
local stream = popen_r(...)
local ret = stream:read('*a')
stream:close()
if ret then
return ret
end
end
print('ERROR: Failed to execute ' .. argss_to_cmd(...) .. ': nil return after 10 attempts')
return nil
end
local function shallowcopy(orig)
if type(orig) ~= 'table' then
return orig
@ -569,6 +614,7 @@ end
return {
REMOVE_THIS = REMOVE_THIS,
argss_to_cmd = argss_to_cmd,
check_cores = check_cores,
check_logs = check_logs,
concat_tables = concat_tables,
@ -590,6 +636,9 @@ return {
mergedicts_copy = mergedicts_copy,
neq = neq,
ok = ok,
popen_r = popen_r,
popen_w = popen_w,
repeated_read_cmd = repeated_read_cmd,
shallowcopy = shallowcopy,
table_flatten = table_flatten,
tmpname = tmpname,

View File

@ -1,4 +1,6 @@
#define _GNU_SOURCE
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include <sys/stat.h>
static const mode_t kS_IFMT = S_IFMT;

View File

@ -779,7 +779,7 @@ local function gen_itp(it)
end
local function cppimport(path)
return cimport(Paths.test_include_path .. '/' .. path)
return cimport(Paths.test_source_path .. '/test/includes/pre/' .. path)
end
cimport('./src/nvim/types.h', './src/nvim/main.h', './src/nvim/os/time.h')

View File

@ -113,7 +113,7 @@ end
describe('file_open_fd', function()
itp('can use file descriptor returned by os_open for reading', function()
local fd = m.os_open(file1, m.kO_RDONLY, 0)
local err, fp = file_open_fd(fd, false)
local err, fp = file_open_fd(fd, m.kFileReadOnly)
eq(0, err)
eq({#fcontents, fcontents}, {file_read(fp, #fcontents)})
eq(0, m.file_close(fp, false))
@ -121,7 +121,7 @@ describe('file_open_fd', function()
itp('can use file descriptor returned by os_open for writing', function()
eq(nil, lfs.attributes(filec))
local fd = m.os_open(filec, m.kO_WRONLY + m.kO_CREAT, 384)
local err, fp = file_open_fd(fd, true)
local err, fp = file_open_fd(fd, m.kFileWriteOnly)
eq(0, err)
eq(4, file_write(fp, 'test'))
eq(0, m.file_close(fp, false))
@ -133,7 +133,7 @@ end)
describe('file_open_fd_new', function()
itp('can use file descriptor returned by os_open for reading', function()
local fd = m.os_open(file1, m.kO_RDONLY, 0)
local err, fp = file_open_fd_new(fd, false)
local err, fp = file_open_fd_new(fd, m.kFileReadOnly)
eq(0, err)
eq({#fcontents, fcontents}, {file_read(fp, #fcontents)})
eq(0, m.file_free(fp, false))
@ -141,7 +141,7 @@ describe('file_open_fd_new', function()
itp('can use file descriptor returned by os_open for writing', function()
eq(nil, lfs.attributes(filec))
local fd = m.os_open(filec, m.kO_WRONLY + m.kO_CREAT, 384)
local err, fp = file_open_fd_new(fd, true)
local err, fp = file_open_fd_new(fd, m.kFileWriteOnly)
eq(0, err)
eq(4, file_write(fp, 'test'))
eq(0, m.file_free(fp, false))

View File

@ -390,7 +390,7 @@ describe('fs.c', function()
buf = ffi.new('char[?]', size + 1, ('\0'):rep(size))
end
local eof = ffi.new('bool[?]', 1, {true})
local ret2 = fs.os_read(fd, eof, buf, size)
local ret2 = fs.os_read(fd, eof, buf, size, false)
local ret1 = eof[0]
local ret3 = ''
if buf ~= nil then
@ -408,7 +408,7 @@ describe('fs.c', function()
end
local iov = ffi.new('struct iovec[?]', #sizes, bufs)
local eof = ffi.new('bool[?]', 1, {true})
local ret2 = fs.os_readv(fd, eof, iov, #sizes)
local ret2 = fs.os_readv(fd, eof, iov, #sizes, false)
local ret1 = eof[0]
local ret3 = {}
for i = 1,#sizes do
@ -418,7 +418,7 @@ describe('fs.c', function()
return ret1, ret2, ret3
end
local function os_write(fd, data)
return fs.os_write(fd, data, data and #data or 0)
return fs.os_write(fd, data, data and #data or 0, false)
end
describe('os_path_exists', function()
@ -491,6 +491,22 @@ describe('fs.c', function()
end)
end)
describe('os_dup', function()
itp('returns new file descriptor', function()
local dup0 = fs.os_dup(0)
local dup1 = fs.os_dup(1)
local dup2 = fs.os_dup(2)
local tbl = {[0]=true, [1]=true, [2]=true,
[tonumber(dup0)]=true, [tonumber(dup1)]=true,
[tonumber(dup2)]=true}
local i = 0
for _, _ in pairs(tbl) do
i = i + 1
end
eq(i, 6) -- All fds must be unique
end)
end)
describe('os_open', function()
local new_file = 'test_new_file'
local existing_file = 'unit-test-directory/test_existing.file'

View File

@ -2,6 +2,10 @@
-- windows, will probably need quite a bit of adjustment to run there.
local ffi = require("ffi")
local global_helpers = require('test.helpers')
local argss_to_cmd = global_helpers.argss_to_cmd
local repeated_read_cmd = global_helpers.repeated_read_cmd
local ccs = {}
@ -22,15 +26,6 @@ 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)
local 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
local function parse_make_deps(deps)
-- remove line breaks and line concatenators
@ -149,16 +144,6 @@ function Gcc:add_to_include_path(...)
end
end
local function argss_to_cmd(...)
local cmd = ''
for i = 1, select('#', ...) do
for _, arg in ipairs(select(i, ...)) do
cmd = cmd .. ' ' .. shell_quote(arg)
end
end
return cmd
end
-- returns a list of the headers files upon which this file relies
function Gcc:dependencies(hdr)
local cmd = argss_to_cmd(self.path, {'-M', hdr}) .. ' 2>&1'
@ -172,29 +157,15 @@ function Gcc:dependencies(hdr)
end
end
local function repeated_call(...)
local cmd = argss_to_cmd(...)
for _ = 1, 10 do
local stream = io.popen(cmd)
local ret = stream:read('*a')
stream:close()
if ret then
return ret
end
end
print('ERROR: preprocess.lua: Failed to execute ' .. cmd .. ': nil return after 10 attempts')
return nil
end
function Gcc:filter_standard_defines(defines)
if not self.standard_defines then
local pseudoheader_fname = 'tmp_empty_pseudoheader.h'
local pseudoheader_file = io.open(pseudoheader_fname, 'w')
pseudoheader_file:close()
local standard_defines = repeated_call(self.path,
self.preprocessor_extra_flags,
self.get_defines_extra_flags,
{pseudoheader_fname})
local standard_defines = repeated_read_cmd(self.path,
self.preprocessor_extra_flags,
self.get_defines_extra_flags,
{pseudoheader_fname})
os.remove(pseudoheader_fname)
self.standard_defines = {}
for line in standard_defines:gmatch('[^\n]+') do
@ -223,9 +194,9 @@ function Gcc:preprocess(previous_defines, ...)
pseudoheader_file:flush()
pseudoheader_file:close()
local defines = repeated_call(self.path, self.preprocessor_extra_flags,
self.get_defines_extra_flags,
{pseudoheader_fname})
local defines = repeated_read_cmd(self.path, self.preprocessor_extra_flags,
self.get_defines_extra_flags,
{pseudoheader_fname})
defines = self:filter_standard_defines(defines)
-- lfs = require("lfs")
@ -234,9 +205,10 @@ function Gcc:preprocess(previous_defines, ...)
-- io.stderr\write("CWD: #{lfs.currentdir!}\n")
-- io.stderr\write("CMD: #{cmd}\n")
local declarations = repeated_call(self.path, self.preprocessor_extra_flags,
self.get_declarations_extra_flags,
{pseudoheader_fname})
local declarations = repeated_read_cmd(self.path,
self.preprocessor_extra_flags,
self.get_declarations_extra_flags,
{pseudoheader_fname})
os.remove(pseudoheader_fname)