diff options
author | Justin M. Keyes <justinkz@gmail.com> | 2018-04-17 10:33:36 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-04-17 10:33:36 +0200 |
commit | 7a13611ba2033766da8cad73f46362bd01632c2c (patch) | |
tree | 170eff47d8e67ad5e2c484697f4bd999abf9844e | |
parent | 48967695c441d13bc9faa8aae4bed8d6fb5bdc14 (diff) | |
parent | 387fbcd95cade4b0c037d18f404944676a59db09 (diff) | |
download | rneovim-7a13611ba2033766da8cad73f46362bd01632c2c.tar.gz rneovim-7a13611ba2033766da8cad73f46362bd01632c2c.tar.bz2 rneovim-7a13611ba2033766da8cad73f46362bd01632c2c.zip |
Merge #8276 'startup: Make -s - read from stdin'
-rw-r--r-- | src/nvim/getchar.c | 94 | ||||
-rw-r--r-- | src/nvim/getchar.h | 24 | ||||
-rw-r--r-- | src/nvim/globals.h | 6 | ||||
-rw-r--r-- | src/nvim/main.c | 40 | ||||
-rw-r--r-- | src/nvim/message.c | 10 | ||||
-rw-r--r-- | src/nvim/os/fileio.c | 45 | ||||
-rw-r--r-- | src/nvim/os/fileio.h | 3 | ||||
-rw-r--r-- | src/nvim/os/fs.c | 50 | ||||
-rw-r--r-- | test/functional/core/main_spec.lua | 131 | ||||
-rw-r--r-- | test/helpers.lua | 53 | ||||
-rw-r--r-- | test/includes/pre/sys/stat.h | 4 | ||||
-rw-r--r-- | test/unit/helpers.lua | 2 | ||||
-rw-r--r-- | test/unit/os/fileio_spec.lua | 8 | ||||
-rw-r--r-- | test/unit/os/fs_spec.lua | 22 | ||||
-rw-r--r-- | test/unit/preprocess.lua | 58 |
15 files changed, 398 insertions, 152 deletions
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 03929a58b6..98164b2653 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -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); } diff --git a/src/nvim/getchar.h b/src/nvim/getchar.h index e634273e0d..38a2e75663 100644 --- a/src/nvim/getchar.h +++ b/src/nvim/getchar.h @@ -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" diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 89d93310a6..56790bc89b 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -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)")); diff --git a/src/nvim/main.c b/src/nvim/main.c index b7a4caaab1..a4ed868af1 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -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(); diff --git a/src/nvim/message.c b/src/nvim/message.c index 04528629c7..3a3c9cb167 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -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 */ { diff --git a/src/nvim/os/fileio.c b/src/nvim/os/fileio.c index a95adc86b6..ccf35fd57c 100644 --- a/src/nvim/os/fileio.c +++ b/src/nvim/os/fileio.c @@ -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); diff --git a/src/nvim/os/fileio.h b/src/nvim/os/fileio.h index 0b55cc695f..7c53cd4f07 100644 --- a/src/nvim/os/fileio.h +++ b/src/nvim/os/fileio.h @@ -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) diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index b7c2714296..0414794d01 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -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; diff --git a/test/functional/core/main_spec.lua b/test/functional/core/main_spec.lua new file mode 100644 index 0000000000..2c4d110f0f --- /dev/null +++ b/test/functional/core/main_spec.lua @@ -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) diff --git a/test/helpers.lua b/test/helpers.lua index e0645d083f..efc0e911f1 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -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, diff --git a/test/includes/pre/sys/stat.h b/test/includes/pre/sys/stat.h index c6cac80913..1cb811d579 100644 --- a/test/includes/pre/sys/stat.h +++ b/test/includes/pre/sys/stat.h @@ -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; diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua index 5cc2be50b1..f8143a0125 100644 --- a/test/unit/helpers.lua +++ b/test/unit/helpers.lua @@ -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') diff --git a/test/unit/os/fileio_spec.lua b/test/unit/os/fileio_spec.lua index d9c98e8afa..4d58a8934e 100644 --- a/test/unit/os/fileio_spec.lua +++ b/test/unit/os/fileio_spec.lua @@ -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)) diff --git a/test/unit/os/fs_spec.lua b/test/unit/os/fs_spec.lua index 78455ee324..ae6dfe6423 100644 --- a/test/unit/os/fs_spec.lua +++ b/test/unit/os/fs_spec.lua @@ -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' diff --git a/test/unit/preprocess.lua b/test/unit/preprocess.lua index 363358d134..1073855a7d 100644 --- a/test/unit/preprocess.lua +++ b/test/unit/preprocess.lua @@ -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) |