Skip to content

Commit e28e787

Browse files
nickandersonclaude
andcommitted
Fixed usemodule() reporting success when the module exited non-zero
usemodule() ignored the module's exit status and defined its class either way. It returns false now when the module ran and failed, and only fails as a function when the module could not be run or read at all. The distinction matters: a failed function call leaves the class undefined rather than false, so "not => usemodule(...)" would not have become true either. The test could not pass or fail whatever the code did. It looked for the modules one directory too high, its Pass guard asked for a class and its own negation, and the module it runs exits 1 on purpose which failed the whole test bundle. With those corrected it fails on the old code and passes on the new. Ticket: CFE-942 Changelog: Title Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit a5b28ff)
1 parent 8a337ed commit e28e787

2 files changed

Lines changed: 27 additions & 12 deletions

File tree

libpromises/evalfunction.c

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ static char *StripPatterns(char *file_buffer, const char *pattern, const char *f
100100
static int BuildLineArray(EvalContext *ctx, const Bundle *bundle, const char *array_lval, const char *file_buffer,
101101
const char *split, int maxent, DataType type, bool int_index);
102102
static JsonElement* BuildData(EvalContext *ctx, const char *file_buffer, const char *split, int maxent, bool make_array);
103-
static bool ExecModule(EvalContext *ctx, char *command);
103+
static bool ExecModule(EvalContext *ctx, char *command, int *retcode);
104104

105105
static bool CheckIDChar(const char ch);
106106
static bool CheckID(const char *id);
@@ -2574,12 +2574,15 @@ static FnCallResult FnCallUseModule(EvalContext *ctx,
25742574

25752575
Log(LOG_LEVEL_VERBOSE, "Executing and using module [%s]", modulecmd);
25762576

2577-
if (!ExecModule(ctx, modulecmd))
2577+
/* A module which exits non-zero has not told us anything we should act on,
2578+
* so the function is false. Not being able to run it at all is a failure. */
2579+
int retcode = 0;
2580+
if (!ExecModule(ctx, modulecmd, &retcode))
25782581
{
25792582
return FnFailure();
25802583
}
25812584

2582-
return FnReturnContext(true);
2585+
return FnReturnContext(retcode == 0);
25832586
}
25842587

25852588
/*********************************************************************/
@@ -8843,7 +8846,7 @@ static FnCallResult FnCallFindfilesUp(ARG_UNUSED EvalContext *ctx, ARG_UNUSED co
88438846

88448847
/*********************************************************************/
88458848

8846-
static bool ExecModule(EvalContext *ctx, char *command)
8849+
static bool ExecModule(EvalContext *ctx, char *command, int *retcode)
88478850
{
88488851
FILE *pp = cf_popen(command, "rt", true);
88498852
if (!pp)
@@ -8874,7 +8877,7 @@ static bool ExecModule(EvalContext *ctx, char *command)
88748877
ModuleProtocol(ctx, command, line, print, context, sizeof(context), tags, &persistence);
88758878
}
88768879
bool atend = feof(pp);
8877-
cf_pclose(pp);
8880+
*retcode = cf_pclose(pp);
88788881
free(line);
88798882
StringSetDestroy(tags);
88808883

@@ -8884,6 +8887,11 @@ static bool ExecModule(EvalContext *ctx, char *command)
88848887
return false;
88858888
}
88868889

8890+
if (*retcode != 0)
8891+
{
8892+
Log(LOG_LEVEL_ERR, "Module '%s' returned non-zero exit code %d", command, *retcode);
8893+
}
8894+
88878895
return true;
88888896
}
88898897

tests/acceptance/02_classes/02_functions/usemodule-returns-nonzero.cf

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,8 @@ bundle agent test
4242
"description" -> { "CFE-942" }
4343
string => "Test that when a module executed by usemodule() returns nonzero, it's not interpreted as successful";
4444

45-
"test_soft_fail"
46-
string => "any",
47-
meta => { "CFE-942" },
48-
comment => "usemodule seems to return true no matter if the module exists returning 0 or nonzero";
45+
# The modules this test writes are /bin/sh scripts
46+
"test_skip_unsupported" string => "windows";
4947

5048
classes:
5149

@@ -54,26 +52,35 @@ bundle agent test
5452
"usemodule_expect_no_class_defined_because_return_nonzero"
5553
expression => usemodule( "foo-usemodule", "" ),
5654
scope => "namespace",
57-
if => isexecutable( "$(sys.workdir)/foo-usemodule" );
55+
if => isexecutable( "$(sys.workdir)/modules/foo-usemodule" );
5856

5957
# Since the module exists non zero, we should get this class
6058
"usemodule_expect_class_defined_because_return_nonzero"
6159
not => usemodule( "foo-usemodule", "" ),
6260
scope => "namespace",
63-
if => isexecutable( "$(sys.workdir)/foo-usemodule" );
61+
if => isexecutable( "$(sys.workdir)/modules/foo-usemodule" );
6462

6563
commands:
6664
"$(sys.workdir)/modules/foo-commands_module"
6765
module => "true",
66+
classes => expected_nonzero_exit,
6867
if => isexecutable( $(this.promiser) );
6968
}
69+
70+
body classes expected_nonzero_exit
71+
# @brief The module here exits non-zero on purpose, so don't fail the promise
72+
# over it and take the whole test bundle down with it
73+
{
74+
kept_returncodes => { "1" };
75+
}
76+
7077
bundle agent check
7178
{
7279
methods:
7380
usemodule_expect_no_class_defined_because_return_nonzero.!usemodule_expect_class_defined_because_return_nonzero::
7481
"FAIL" usebundle => dcs_fail( $(this.promise_filename) );
7582

76-
!usemodule_expect_class_defined_because_return_nonzero.usemodule_expect_class_defined_because_return_nonzero::
83+
!usemodule_expect_no_class_defined_because_return_nonzero.usemodule_expect_class_defined_because_return_nonzero::
7784
"Pass" usebundle => dcs_pass( $(this.promise_filename) );
7885

7986
reports:

0 commit comments

Comments
 (0)