From 04f5537951e77a03d69d731136980c4e00402aca Mon Sep 17 00:00:00 2001 From: David Anderson Date: Mon, 12 Feb 2024 01:56:48 -0800 Subject: [PATCH] client: make the --app_test feature more general. This feature lets you run an app under a BOINC client without having a project. You describe the app (i.e. its input/output files) in C++ code, in the function app_test_init(). I used this to debug sporadic apps and VM aps. This PR is a rewrite of app_test_init() to make it clearer, and to facilitate describing apps with input and output files. --- client/app_test.cpp | 143 +++++++++++++++++++++++++++++++--------- client/client_types.cpp | 6 +- client/client_types.h | 13 +++- 3 files changed, 123 insertions(+), 39 deletions(-) diff --git a/client/app_test.cpp b/client/app_test.cpp index d1a70070893..ca747373928 100644 --- a/client/app_test.cpp +++ b/client/app_test.cpp @@ -15,9 +15,29 @@ // You should have received a copy of the GNU Lesser General Public License // along with BOINC. If not, see . -// Set up data structures (project, app, app version, WU, result etc.) -// so that the client runs the given executable. -// Lets you debug client/app interactions with no server or fake XML files +// A framework that lets you run jobs under a BOINC client +// without a project, and without fake XML files +// Lets you debug client/app interactions. +// +// To use this framework: +// edit this file to describe your application: +// input/output files, attributes, etc. +// NOTE: currently it's set up for the 'uc2' app, +// which reads 'in' and writes 'out' (logical names). +// The job uses physical names 'infile' and 'outfile'. +// build the BOINC client +// make a 'test' directory +// (or you can use an existing BOINC data directory, +// in which case the client will also run jobs that are there) +// make a directory test/slots/app_test +// The client will run the test job there. +// Clean it out between runs. +// put the executable file and input file(s) in the test directory +// (which acts as the project directory) +// in the test directory, run boinc --app_test +// when the job is done, the client won't clean out the slot dir. +// You can examine the contents of the slot dir, +// and examine the output files in the test dir. #include "project.h" #include "client_types.h" @@ -25,73 +45,132 @@ #include "client_state.h" #include "log_flags.h" -void CLIENT_STATE::app_test_init() { +static PROJECT* make_project() { PROJECT *proj = new PROJECT; strcpy(proj->project_name, "test project"); strcpy(proj->master_url, "test_project_url"); strcpy(proj->_project_dir, "."); proj->app_test = true; proj->non_cpu_intensive = false; - projects.push_back(proj); + gstate.projects.push_back(proj); + return proj; +} +static APP* make_app(PROJECT* proj) { APP *app = new APP; strcpy(app->name, "test app"); strcpy(app->user_friendly_name, "test app"); app->project = proj; - // can put other stuff here like - app->sporadic = true; - have_sporadic_app = true; - apps.push_back(app); + gstate.apps.push_back(app); + return app; +} - FILE_INFO *fip = new FILE_INFO; - strcpy(fip->name, app_test_file.c_str()); - fip->status = FILE_PRESENT; - fip->executable = true; - file_infos.push_back(fip); +#define INPUT_FILE 0 +#define OUTPUT_FILE 1 +#define EXEC_FILE 2 +static FILE_REF* make_file( + PROJECT *proj, const char* phys_name, const char* log_name, int ftype +) { + FILE_INFO *fip = new FILE_INFO; + strcpy(fip->name, phys_name); + fip->project = proj; + fip->status = (ftype == OUTPUT_FILE)?FILE_NOT_PRESENT:FILE_PRESENT; + if (ftype == EXEC_FILE) fip->executable = true; + if (ftype == OUTPUT_FILE) { + fip->max_nbytes = 1e9; + fip->upload_urls.add(string("foobar")); + } + gstate.file_infos.push_back(fip); FILE_REF * fref = new FILE_REF; + if (log_name) { + strcpy(fref->open_name, log_name); + } fref->file_info = fip; - strcpy(fip->name, app_test_file.c_str()); - fref->main_program = true; + if (ftype == EXEC_FILE) fref->main_program = true; + return fref; +} +static APP_VERSION* make_app_version(APP *app, const char* exec_name) { APP_VERSION *av = new APP_VERSION; strcpy(av->app_name, "test_av"); strcpy(av->api_version, "8.0"); av->app = app; - av->project = proj; - av->app_files.push_back(*fref); - // can put other stuff here like + av->project = app->project; av->avg_ncpus = 1; av->flops = 1e9; -#if 0 - av->gpu_ram = 1e7; - av->gpu_usage.rsc_type = PROC_TYPE_NVIDIA_GPU; - av->gpu_usage.usage = 1; -#endif - app_versions.push_back(av); + FILE_REF *fref = make_file(app->project, exec_name, NULL, EXEC_FILE); + av->app_files.push_back(*fref); + gstate.app_versions.push_back(av); + return av; +} +static WORKUNIT* make_workunit(APP* app) { WORKUNIT *wu = new WORKUNIT; strcpy(wu->name, "test_wu"); strcpy(wu->app_name, "test_app"); - wu->project = proj; wu->app = app; + wu->project = app->project; wu->rsc_fpops_est = 1e9; wu->rsc_fpops_bound = 1e12; wu->rsc_memory_bound = 1e9; wu->rsc_disk_bound = 1e9; - wu->command_line = "--sporadic"; - workunits.push_back(wu); + gstate.workunits.push_back(wu); + return wu; +} +static RESULT* make_result(APP_VERSION *av, WORKUNIT* wu) { RESULT *res = new RESULT; strcpy(res->name, "test_result"); strcpy(res->wu_name, "test_wu"); - res->project = proj; + res->project = av->project; res->avp = av; res->wup = wu; - res->app = app; + res->app = av->app; res->report_deadline = dtime()+86400; - results.push_back(res); + gstate.results.push_back(res); + return res; +} + +// app_test_init() sets up data structures +// (project, app, app version, WU, result) +// so that the client runs a test job. +// +void CLIENT_STATE::app_test_init() { + PROJECT *proj = make_project(); + + APP *app = make_app(proj); + // can put other stuff here like +#if 0 + app->sporadic = true; + have_sporadic_app = true; +#endif + + APP_VERSION *av = make_app_version(app, app_test_file.c_str()); + // can put other stuff here like +#if 0 + av->gpu_ram = 1e7; + av->gpu_usage.rsc_type = PROC_TYPE_NVIDIA_GPU; + av->gpu_usage.usage = 1; +#endif + + WORKUNIT *wu = make_workunit(app); +#if 1 + wu->command_line = "in out"; + wu->input_files.push_back( + *make_file(proj, "infile", "in", INPUT_FILE) + ); +#endif + + RESULT *result = make_result(av, wu); +#if 1 + result->output_files.push_back( + *make_file(proj, "outfile", "out", OUTPUT_FILE) + ); +#endif - network_suspended = true; + // tell the client not to get work or run benchmarks + // + cc_config.unsigned_apps_ok = true; cc_config.skip_cpu_benchmarks = true; } diff --git a/client/client_types.cpp b/client/client_types.cpp index 2bac026ccf7..49ce09cf26a 100644 --- a/client/client_types.cpp +++ b/client/client_types.cpp @@ -1093,11 +1093,7 @@ void APP_VERSION::check_graphics_exec() { int FILE_REF::parse(XML_PARSER& xp) { bool temp; - safe_strcpy(file_name, ""); - safe_strcpy(open_name, ""); - main_program = false; - copy_file = false; - optional = false; + clear(); while (!xp.get_tag()) { if (xp.match_tag("/file_ref")) { if (strstr(open_name, "..")) return ERR_BAD_FILENAME; diff --git a/client/client_types.h b/client/client_types.h index 7a444078170..d0acfab3765 100644 --- a/client/client_types.h +++ b/client/client_types.h @@ -101,7 +101,7 @@ struct URL_LIST { }; struct FILE_INFO { - char name[256]; + char name[256]; // physical name char md5_cksum[MD5_LEN]; double max_nbytes; double nbytes; @@ -177,7 +177,7 @@ struct FILE_INFO { // struct FILE_REF { char file_name[256]; - // physical name + // physical name; should match file_info->name char open_name[256]; // logical name bool main_program; @@ -187,6 +187,15 @@ struct FILE_REF { bool optional; // for output files: app may not generate file; // don't treat as error if file is missing. + inline void clear() { + safe_strcpy(file_name, ""); + safe_strcpy(open_name, ""); + main_program = false; + file_info = NULL; + copy_file = false; + optional = false; + } + FILE_REF() {clear();} int parse(XML_PARSER&); int write(MIOFILE&); };