| // Copyright 2019 The Fuchsia Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include <lib/async-loop/cpp/loop.h> | 
 | #include <lib/async-loop/default.h> | 
 | #include <lib/fdio/spawn.h> | 
 | #include <zircon/process.h> | 
 | #include <zircon/syscalls.h> | 
 |  | 
 | #include <array> | 
 | #include <fstream> | 
 | #include <string> | 
 |  | 
 | #include <gtest/gtest.h> | 
 | #include <task-utils/walker.h> | 
 |  | 
 | #include "src/developer/shell/josh/lib/js_testing_utils.h" | 
 | #include "zx.h" | 
 |  | 
 | namespace shell { | 
 |  | 
 | class TaskTest : public JsTest { | 
 |  protected: | 
 |   void SetUp() override { JsTest::SetUp(); } | 
 | }; | 
 |  | 
 | TEST_F(TaskTest, SimplePs) { | 
 |   InitBuiltins("/pkg/data/fidling", "/pkg/data/lib"); | 
 |  | 
 |   // Loop up-front to populate the svc object, which is done via a promise. | 
 |   js_std_loop(ctx_->Get()); | 
 |   std::string test_string = R"( | 
 |       globalThis.resultOne = undefined; | 
 |       task.ps(). | 
 |         then((result) => { | 
 |             globalThis.resultOne = result; }). | 
 |         catch((e) => { std.printf(e); std.printf(e.stack); globalThis.resultOne = e;}); | 
 |   )"; | 
 |   ASSERT_TRUE(Eval(test_string)); | 
 |   js_std_loop(ctx_->Get()); | 
 |   test_string = R"( | 
 |       let res = globalThis.resultOne; | 
 |       if (res instanceof Error) { | 
 |         throw res; | 
 |       } | 
 |       if (res.size <= 0) { | 
 |         throw "No tasks found by ps?"; | 
 |       } | 
 |       res.forEach((value, key, map) => { | 
 |           if (!key.hasOwnProperty("name") || !key.hasOwnProperty("info")) { | 
 |               throw "Missing task information in " + JSON.stringify(key); | 
 |           } | 
 |        }); | 
 |   )"; | 
 |   ASSERT_TRUE(Eval(test_string)); | 
 | } | 
 |  | 
 | TEST_F(TaskTest, Kill) { | 
 |   async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); | 
 |   ASSERT_EQ(loop.StartThread(), ZX_OK); | 
 |   InitBuiltins("/pkg/data/fidling", "/pkg/data/lib"); | 
 |  | 
 |   // get root job, to create a subprocess of it, that will be dicoverable in the job tree | 
 |   js_std_loop(ctx_->Get()); | 
 |   std::string test_string0 = R"( | 
 |       fidl.loadLibrary('fuchsia.kernel'); | 
 |       let promiseRootJobResult = svc.fuchsia_kernel_RootJob.Get(); | 
 |       promiseRootJobResult. | 
 |               then((result) => { | 
 |                   globalThis.resultOne = result; }) | 
 |   )"; | 
 |   ASSERT_TRUE(Eval(test_string0)); | 
 |   js_std_loop(ctx_->Get()); | 
 |   std::string test_string1 = R"( | 
 |       globalThis.resultOne['job']._handle; | 
 |   )"; | 
 |   JSValue handle_to_root_js = | 
 |       JS_Eval(ctx_->Get(), test_string1.c_str(), test_string1.length(), "<evalScript>", 0); | 
 |   if (JS_IsException(handle_to_root_js)) { | 
 |     ctx_->DumpError(); | 
 |   } | 
 |   ASSERT_FALSE(JS_IsException(handle_to_root_js)); | 
 |   zx_handle_disposition_t handle_to_root = zx::HandleFromJsval(handle_to_root_js); | 
 |  | 
 |   // spawn a proccess, child of root job and get its koid | 
 |   const char* argv[] = {"/pkg/bin/spawn_child_test_util", nullptr}; | 
 |   std::vector<fdio_spawn_action_t> actions; | 
 |   char err_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH]; | 
 |   zx_handle_t process_handle; | 
 |   zx_status_t status2 = fdio_spawn_etc(handle_to_root.handle, FDIO_SPAWN_CLONE_ALL, argv[0], argv, | 
 |                                        nullptr,  // Environ | 
 |                                        actions.size(), actions.data(), &process_handle, err_msg); | 
 |   ASSERT_EQ(status2, ZX_OK) << "Failed to spawn command (" << status2 << "): " << err_msg; | 
 |   zx_info_handle_basic_t info; | 
 |   ASSERT_EQ( | 
 |       zx_object_get_info(process_handle, ZX_INFO_HANDLE_BASIC, &info, sizeof(info), NULL, NULL), | 
 |       ZX_OK); | 
 |   loop.RunUntilIdle(); | 
 |  | 
 |   // kill the process | 
 |   std::string test_string2 = | 
 |       "globalThis.resultTwo = undefined;" | 
 |       "task.kill(" + | 
 |       std::to_string(info.koid) + | 
 |       ").then((result) => {" | 
 |       "globalThis.resultTwo = result; })." | 
 |       "catch((e) => { std.printf(e); std.printf(e.stack); globalThis.resultTwo = e;});"; | 
 |   ASSERT_TRUE(Eval(test_string2)); | 
 |   // task.kill() is async, the loop is needed to ensure it is executed | 
 |   js_std_loop(ctx_->Get()); | 
 |   std::string test_string3 = R"( | 
 |       let res = globalThis.resultTwo; | 
 |       if (res instanceof Error) { | 
 |         throw res; | 
 |       } | 
 |       if (res != undefined) { | 
 |         throw res; | 
 |       } | 
 |   )"; | 
 |   ASSERT_TRUE(Eval(test_string3)); | 
 | } | 
 |  | 
 | TEST_F(TaskTest, KillAll) { | 
 |   async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); | 
 |   ASSERT_EQ(loop.StartThread(), ZX_OK); | 
 |   InitBuiltins("/pkg/data/fidling", "/pkg/data/lib"); | 
 |  | 
 |   // get root job, to create a subprocess of it, that will be dicoverable in the job tree | 
 |   js_std_loop(ctx_->Get()); | 
 |   std::string test_string0 = R"( | 
 |       fidl.loadLibrary('fuchsia.kernel'); | 
 |       let promiseRootJobResult = svc.fuchsia_kernel_RootJob.Get(); | 
 |       promiseRootJobResult. | 
 |               then((result) => { | 
 |                   globalThis.resultOne = result; }) | 
 |   )"; | 
 |   ASSERT_TRUE(Eval(test_string0)); | 
 |   js_std_loop(ctx_->Get()); | 
 |   std::string test_string1 = R"( | 
 |       globalThis.resultOne['job']._handle; | 
 |   )"; | 
 |   JSValue handle_to_root_js = | 
 |       JS_Eval(ctx_->Get(), test_string1.c_str(), test_string1.length(), "<evalScript>", 0); | 
 |   if (JS_IsException(handle_to_root_js)) { | 
 |     ctx_->DumpError(); | 
 |   } | 
 |   ASSERT_FALSE(JS_IsException(handle_to_root_js)); | 
 |   zx_handle_disposition_t handle_to_root = zx::HandleFromJsval(handle_to_root_js); | 
 |  | 
 |   // get koid of current process to make the spawned name unique | 
 |   zx_handle_t cur_process_handle = zx_process_self(); | 
 |   zx_info_handle_basic_t info_cur_process; | 
 |   ASSERT_EQ(zx_object_get_info(cur_process_handle, ZX_INFO_HANDLE_BASIC, &info_cur_process, | 
 |                                sizeof(info_cur_process), NULL, NULL), | 
 |             ZX_OK); | 
 |  | 
 |   // spawn a proccess, child of root job, named spawnChild+cur_process_koid | 
 |   const char* argv[] = {"/pkg/bin/spawn_child_test_util", nullptr}; | 
 |   std::string process_name = "spawnChild" + std::to_string(info_cur_process.koid); | 
 |   std::vector<fdio_spawn_action_t> actions; | 
 |   actions.push_back({.action = FDIO_SPAWN_ACTION_SET_NAME, .name = {.data = process_name.c_str()}}); | 
 |   char err_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH]; | 
 |   zx_handle_t process_handle; | 
 |   zx_status_t status2 = fdio_spawn_etc(handle_to_root.handle, FDIO_SPAWN_CLONE_ALL, argv[0], argv, | 
 |                                        nullptr,  // Environ | 
 |                                        actions.size(), actions.data(), &process_handle, err_msg); | 
 |   ASSERT_EQ(status2, ZX_OK) << "Failed to spawn command (" << status2 << "): " << err_msg; | 
 |   loop.RunUntilIdle(); | 
 |  | 
 |   // kill the process by name | 
 |   std::string test_string2 = | 
 |       "globalThis.resultTwo = undefined;" | 
 |       "task.killall(\"" + | 
 |       process_name + | 
 |       "\").then((result) => {" | 
 |       "globalThis.resultTwo = result; })." | 
 |       "catch((e) => { std.printf(e); std.printf(e.stack); globalThis.resultTwo = e;});"; | 
 |   ASSERT_TRUE(Eval(test_string2)); | 
 |   // task.kill() is async, the loop is needed to ensure it is executed | 
 |   js_std_loop(ctx_->Get()); | 
 |   std::string test_string3 = R"( | 
 |       let res = globalThis.resultTwo; | 
 |       if (res instanceof Error) { | 
 |         throw res; | 
 |       } | 
 |       if (res != undefined) { | 
 |         throw res; | 
 |       } | 
 |   )"; | 
 |   ASSERT_TRUE(Eval(test_string3)); | 
 |  | 
 |   // launch the same process again, to kill it using a regex | 
 |   status2 = fdio_spawn_etc(handle_to_root.handle, FDIO_SPAWN_CLONE_ALL, argv[0], argv, | 
 |                            nullptr,  // Environ | 
 |                            actions.size(), actions.data(), &process_handle, err_msg); | 
 |   ASSERT_EQ(status2, ZX_OK) << "Failed to spawn command (" << status2 << "): " << err_msg; | 
 |   loop.RunUntilIdle(); | 
 |  | 
 |   // kill the process by regex | 
 |   std::string test_string4 = | 
 |       "globalThis.resultTwo = undefined;" | 
 |       "task.killall(\"[a-z]" + | 
 |       process_name.substr(1) + | 
 |       "\", \"r\").then((result) => {" | 
 |       "globalThis.resultTwo = result; })." | 
 |       "catch((e) => { std.printf(e); std.printf(e.stack); globalThis.resultTwo = e;});"; | 
 |   ASSERT_TRUE(Eval(test_string4)); | 
 |   // task.kill() is async, the loop is needed to ensure it is executed | 
 |   js_std_loop(ctx_->Get()); | 
 |   std::string test_string5 = R"( | 
 |       res = globalThis.resultTwo; | 
 |       if (res instanceof Error) { | 
 |         throw res; | 
 |       } | 
 |       if (res != undefined) { | 
 |         throw res; | 
 |       } | 
 |   )"; | 
 |   ASSERT_TRUE(Eval(test_string5)); | 
 | } | 
 |  | 
 | }  // namespace shell |