# # # patch "NEWS" # from [d7967f2eb3746ce5cf4e3e2b4c6222ace128c0e7] # to [695e137d4164e3b4c97f061d4d960c1bc7261f28] # # patch "cmd_automate.cc" # from [6bbbc31ad43b38d1302a66d9159161ae0a94af2c] # to [77754e06f9f2c2b14faf1b4ab0fdebd62ba3e711] # # patch "monotone.texi" # from [3e387ce60d7cbc35d847fac60dc30a1e1e5320ce] # to [b7db5663aa4fcde1109b8f62324f73948d7c21db] # # patch "tests/automate_stdio/__driver__.lua" # from [9c50b741496b9bcb989ffb813eec19ef5827dcd6] # to [9264b94767d6ced5dc815494bc99d021a6899027] # ============================================================ --- NEWS d7967f2eb3746ce5cf4e3e2b4c6222ace128c0e7 +++ NEWS 695e137d4164e3b4c97f061d4d960c1bc7261f28 @@ -6,6 +6,15 @@ Bugs fixed + - Several bugfixes to `mtn automate stdio': + * it now correctly distinguishs between syntax and command errors by + returning error code 1 for the former and error code 2 for the + latter - just as advertised in the documentation. + * the stdio event loop no longer quits if a syntax error occurs, but + rather discards the wrong state and accepts new (valid) commands. + * option errors haven't been catched properly and thus weren't encoded + in stdio either; this has been fixed as well. + New features - The `mtn_automate' lua function now correctly parses and sets ============================================================ --- cmd_automate.cc 6bbbc31ad43b38d1302a66d9159161ae0a94af2c +++ cmd_automate.cc 77754e06f9f2c2b14faf1b4ab0fdebd62ba3e711 @@ -236,8 +236,14 @@ public: { cmdline.push_back(item); } + E(cmdline.size() > 0, + F("Bad input to automate stdio: command name is missing")); return true; } + void reset() + { + loc = none; + } }; struct automate_streambuf : public std::streambuf @@ -357,75 +363,92 @@ CMD_AUTOMATE(stdio, "", automate_reader ar(std::cin); vector > params; vector cmdline; - while(ar.get_command(params, cmdline))//while(!EOF) + while (true) { + command const * cmd = 0; + command_id id; args_vector args; - vector::iterator i = cmdline.begin(); - E(i != cmdline.end(), - F("Bad input to automate stdio: command name is missing")); - for (; i != cmdline.end(); ++i) - { - args.push_back(arg_type(*i)); - } + + // stdio decoding errors should be noted with errno 1, + // errno 2 is reserved for errors coming from the commands itself try { - options::options_type opts; - opts = options::opts::all_options() - options::opts::globals(); - opts.instantiate(&app.opts).reset(); + if (!ar.get_command(params, cmdline)) + break; - command_id id; - for (args_vector::const_iterator iter = args.begin(); - iter != args.end(); iter++) - id.push_back(utf8((*iter)())); + vector::iterator i = cmdline.begin(); + for (; i != cmdline.end(); ++i) + { + args.push_back(arg_type(*i)); + id.push_back(utf8(*i)); + } - if (!id.empty()) + set< command_id > matches = + CMD_REF(automate)->complete_command(id); + + if (matches.empty()) { - I(!args.empty()); + N(false, F("no completions for this command")); + } + else if (matches.size() > 1) + { + N(false, F("multiple completions possible for this command")); + } - set< command_id > matches = - CMD_REF(automate)->complete_command(id); + id = *matches.begin(); - if (matches.empty()) - { - N(false, F("no completions for this command")); - } - else if (matches.size() > 1) - { - N(false, F("multiple completions possible for this command")); - } + I(args.size() >= id.size()); + for (command_id::size_type i = 0; i < id.size(); i++) + args.erase(args.begin()); - id = *matches.begin(); + cmd = CMD_REF(automate)->find_command(id); + I(cmd != NULL); - I(args.size() >= id.size()); - for (command_id::size_type i = 0; i < id.size(); i++) - args.erase(args.begin()); + // reset the application's global options + options::options_type opts; + opts = options::opts::all_options() - options::opts::globals(); + opts.instantiate(&app.opts).reset(); - command const * cmd = CMD_REF(automate)->find_command(id); - I(cmd != NULL); - opts = options::opts::globals() | cmd->opts(); + if (cmd->use_workspace_options()) + { + // Re-read the ws options file, rather than just copying + // the options from the previous apts.opts object, because + // the file may have changed due to user activity. + workspace::check_ws_format(); + workspace::get_ws_options(app.opts); + } - if (cmd->use_workspace_options()) - { - // Re-read the ws options file, rather than just copying - // the options from the previous apts.opts object, because - // the file may have changed due to user activity. - workspace::check_ws_format(); - workspace::get_ws_options(app.opts); - } + opts = options::opts::globals() | cmd->opts(); + opts.instantiate(&app.opts).from_key_value_pairs(params); - opts.instantiate(&app.opts).from_key_value_pairs(params); + } + // FIXME: we need to re-package and rethrow this special exception + // since it is not based on informative_failure + catch (option::option_error & e) + { + os.set_err(1); + os<(cmd); - acmd->exec_from_automate(app, id, args, os); - } - else - opts.instantiate(&app.opts).from_key_value_pairs(params); + try + { + automate const * acmd = reinterpret_cast< automate const * >(cmd); + acmd->exec_from_automate(app, id, args, os); } - catch(informative_failure & f) + catch (informative_failure & f) { os.set_err(2); - //Do this instead of printing f.what directly so the output - //will be split into properly-sized blocks automatically. os< is a decimal number specifying which command this output is from. It is 0 for the first command, and increases by one each time. - is 0 for success, 1 for a syntax error, and 2 for any other error. + is 0 for success, 1 for a syntax error which occurred in stdio scope (before the command is executed), and 2 for a command error. is 'l' if this is the last piece of output for this command, and 'm' if there is more output to come. ============================================================ --- tests/automate_stdio/__driver__.lua 9c50b741496b9bcb989ffb813eec19ef5827dcd6 +++ tests/automate_stdio/__driver__.lua 9264b94767d6ced5dc815494bc99d021a6899027 @@ -1,41 +1,55 @@ mtn_setup() mtn_setup() --- a number of broken input strings -check(mtn("automate", "stdio"), 1, false, false, "le") -check(mtn("automate", "stdio"), 1, false, false, "l") -check(mtn("automate", "stdio"), 1, false, false, "l5:a") -check(mtn("automate", "stdio"), 1, false, false, "l5:aaaaaaaa") -check(mtn("automate", "stdio"), 1, false, false, "xl6:leavese") -check(mtn("automate", "stdio"), 1, false, false, "o3:key0:exl6:leavese") -check(mtn("automate", "stdio"), 1, false, false, "o3:ke0:el6:leavese") +function run_stdio(cmd, err) + check(mtn("automate", "stdio"), 0, true, false, cmd) + local parse_stdio = function(dat, which) + local got = {} + local err = 0 + while true do + local b,e,n,r,s = string.find(dat, "(%d+):(%d+):[lm]:(%d+):") + if b == nil then break end + n = n + 0 + if got[n] == nil then got[n] = "" end + got[n] = got[n] .. string.sub(dat, e+1, e+s) + dat = string.sub(dat, e+1+s) + err = tonumber(r) + end + if got[which] == nil then got[which] = "" end + return got[which], err + end + + local o,e = parse_stdio(readfile("stdout"), 0) + check(err == e) + return o +end + +-- a number of broken input strings +run_stdio("le", 1) +run_stdio("l", 1) +run_stdio("l5:a", 1) +run_stdio("l5:aaaaaaaa", 1) +run_stdio("x6:leavese", 1) +run_stdio("o3:key0:ex6:leavese", 1) +run_stdio("o3:ke0:el6:leavese", 1) +-- unknown command +run_stdio("l9:foobarbaze", 1) +-- multiple expansions ('cert' and 'certs') +run_stdio("l3:cere", 1) -- invalid ('leaves' doesn't take --author) -check(mtn("automate", "stdio"), 1, false, false, "o6:author3:fooe l6:leavese") +run_stdio("o6:author3:fooe l6:leavese", 1) --- not broken -check(mtn("automate", "stdio"), 0, false, false, "o3:key0:el6:leavese") -check(mtn("automate", "stdio"), 0, false, false, "o3:key0:e l6:leavese") +-- misuse: 'get_revision' needs an argument +run_stdio("l12:get_revisione", 2) -function parse_stdio(dat, which) - local got = {} - while true do - local b,e,n,s = string.find(dat, "(%d+):%d+:[lm]:(%d+):") - if b == nil then break end - n = n + 0 - if got[n] == nil then got[n] = "" end - got[n] = got[n] .. string.sub(dat, e+1, e+s) - dat = string.sub(dat, e+1+s) - end - if got[which] == nil then got[which] = "" end - L("output of command ", which, ":\n") - L(got[which]) - return got[which] -end +-- not broken +run_stdio("o3:key0:el6:leavese", 0) +run_stdio("o3:key0:e l6:leavese", 0) +-- ensure that we get the output we expect writefile("output", "file contents") - -check(mtn("automate", "inventory"), 0, true, false) +check(mtn("automate", "inventory", "--no-unknown"), 0, true, false) canonicalize("stdout") -rename("stdout", "output") -check(mtn("automate", "stdio"), 0, true, false, "l9:inventorye") -check(parse_stdio(readfile("stdout"), 0) == readfile("output")) +rename("stdout", "stdio-inventory") +check(run_stdio("o10:no-unknown0:e l9:inventorye", 0) == readfile("stdio-inventory")) +