blob: 5cde351e055fc68f7e464ad4a378e60a1eefdfaf [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <gtest/gtest.h>
#include <fcntl.h>
#include "celix/FrameworkFactory.h"
#include "celix/BundleContext.h"
#include "history.h"
class ShellTuiTestSuite : public ::testing::Test {
public:
ShellTuiTestSuite() {
//open pipe to stimulate shell tui input
int fds[2];
int rc = pipe(fds);
EXPECT_EQ(rc, 0) << strerror(errno);
if (rc == 0) {
inputReadFd = fds[0];
inputWriteFd = fds[1];
}
//open pipe to stimulate shell tui output
rc = pipe(fds);
EXPECT_EQ(rc, 0) << strerror(errno);
if (rc == 0) {
outputReadFd = fds[0];
outputWriteFd = fds[1];
//set outputReadFd non blocking
int flags = fcntl(outputReadFd, F_GETFL, 0);
fcntl(outputReadFd, F_SETFL, flags | O_NONBLOCK);
}
}
~ShellTuiTestSuite() override {
close(inputReadFd);
close(inputWriteFd);
close(outputReadFd);
close(outputWriteFd);
}
void createFrameworkWithShellBundles(celix::Properties config = {}, bool configurePipes = false, bool installShell = true, bool installShellTui = true) {
config.set("CELIX_LOGGING_DEFAULT_ACTIVE_LOG_LEVEL", "trace");
if (configurePipes) {
config.set("SHELL_TUI_INPUT_FILE_DESCRIPTOR", std::to_string(inputReadFd).c_str());
config.set("SHELL_TUI_OUTPUT_FILE_DESCRIPTOR", std::to_string(outputWriteFd).c_str());
config.set("SHELL_TUI_ERROR_FILE_DESCRIPTOR", std::to_string(outputWriteFd).c_str());
}
fw = celix::createFramework(config);
ctx = fw->getFrameworkBundleContext();
if (installShell) {
shellBundleId = ctx->installBundle(SHELL_BUNDLE_LOCATION);
EXPECT_GT(shellBundleId, 0);
}
if (installShellTui) {
shellTuiBundleId = ctx->installBundle(SHELL_TUI_BUNDLE_LOCATION);
EXPECT_GT(shellTuiBundleId, 0);
}
}
[[nodiscard]] std::string readPipeOutput() const {
constexpr int BUFSIZE = 1024 * 10;
char buf[BUFSIZE];
buf[BUFSIZE-1] = '\0'; //ensure 0 terminated
auto bytesRead = read(outputReadFd, buf, BUFSIZE-1);
EXPECT_GT(bytesRead, 0);
return std::string{buf};
}
void writeCmd(const char* cmd) const {
write(inputWriteFd, cmd, strlen(cmd)+1);
std::this_thread::sleep_for(std::chrono::milliseconds{100}); //sleep to let command be handled.
}
void testExecuteLb(bool enableAnsiControlSequence) {
celix::Properties config{
{"SHELL_TUI_USE_ANSI_CONTROL_SEQUENCES", enableAnsiControlSequence ? "true" : "false"},
};
createFrameworkWithShellBundles(std::move(config), true);
const char* cmd = "lb\n";
writeCmd(cmd);
auto lbResult = readPipeOutput(); //lb output
std::cout << lbResult << std::endl;
EXPECT_TRUE(strstr(lbResult.c_str(), "Apache Celix Shell TUI")); //lb should print the shell tui name.
}
void testExecuteLbWithoutShell(bool enableAnsiControlSequence) {
celix::Properties config{
{"SHELL_TUI_USE_ANSI_CONTROL_SEQUENCES", enableAnsiControlSequence ? "true" : "false"}
};
createFrameworkWithShellBundles(std::move(config), true, false);
const char* cmd = "lb\n";
writeCmd(cmd);
auto lbResult = readPipeOutput(); //lb output
std::cout << lbResult << std::endl;
EXPECT_TRUE(strstr(lbResult.c_str(), "[Shell TUI] Shell service not available") != nullptr);
}
void testAutoCompleteForCommand(const char* cmd, const char* expectedOutputPart) {
//Test the triggering of auto complete and if the output contains the expected part
celix::Properties config{
{"SHELL_TUI_USE_ANSI_CONTROL_SEQUENCES", "true"},
};
createFrameworkWithShellBundles(std::move(config), true);
writeCmd(cmd);
auto result = readPipeOutput();
std::cout << result << std::endl;
EXPECT_TRUE(strstr(result.c_str(), expectedOutputPart) != nullptr);
}
std::shared_ptr<celix::Framework> fw{};
std::shared_ptr<celix::BundleContext> ctx{};
long shellBundleId = -1;
long shellTuiBundleId = -1;
int inputReadFd = -1;
int inputWriteFd = -1;
int outputReadFd = -1;
int outputWriteFd = -1;
};
TEST_F(ShellTuiTestSuite, StartStopTest) {
//simple start/stop bundles, but should not leak
createFrameworkWithShellBundles();
}
TEST_F(ShellTuiTestSuite, ExecuteLbTest) {
testExecuteLb(false);
}
TEST_F(ShellTuiTestSuite, ExecuteLbWithAnsiControlEnabledTest) {
testExecuteLb(true);
}
TEST_F(ShellTuiTestSuite, AutoCompleteHelpCommandTest) {
//note incomplete command with a tab -> should complete command to `help`
testAutoCompleteForCommand("hel\t", "help");
}
TEST_F(ShellTuiTestSuite, AutoCompleteCelixLbCommandTest) {
//note incomplete command with a tab -> should complete command to `celix::help`
testAutoCompleteForCommand("celix::hel\t", "celix::help");
}
TEST_F(ShellTuiTestSuite, AutoCompleteLbUsageCommandTest) {
//note complete help command with a tab -> should print usage
testAutoCompleteForCommand("help \t", "Usage:");
}
TEST_F(ShellTuiTestSuite, AutoCompleteCelixLbUsageCommandTest) {
//note complete celix::help command with a tab -> should print usage
testAutoCompleteForCommand("celix::help \t", "Usage:");
}
TEST_F(ShellTuiTestSuite, ShellTuiWithInvalidFDTest) {
celix::Properties config{
{"SHELL_TUI_USE_ANSI_CONTROL_SEQUENCES", "true"},
{"SHELL_TUI_INPUT_FILE_DESCRIPTOR", "555"}, //note invalid fd
{"SHELL_TUI_OUTPUT_FILE_DESCRIPTOR", "556"}, //note invalid fd
{"SHELL_TUI_ERROR_FILE_DESCRIPTOR", "557"} //note invalid fd
};
createFrameworkWithShellBundles(std::move(config));
writeCmd("lb\n");
}
TEST_F(ShellTuiTestSuite, ShellTuiWithoutShellTest) {
testExecuteLbWithoutShell(false);
}
TEST_F(ShellTuiTestSuite, ShellTuiWithAnsiControlWithoutShellTest) {
testExecuteLbWithoutShell(true);
}
TEST_F(ShellTuiTestSuite, AnsiControlTest) {
celix::Properties config{
{"SHELL_TUI_USE_ANSI_CONTROL_SEQUENCES", "true"}
};
createFrameworkWithShellBundles(std::move(config), true);
//this test triggers the handling of ansi control sequences, but currently does not test the output
//build history
const char* cmd = "lb\n";
writeCmd(cmd);
cmd = "\033[A"; //up
writeCmd(cmd);
cmd = "\033[C"; //right
writeCmd(cmd);
cmd = "\033[D"; //left
writeCmd(cmd);
cmd = "\033[3"; //del1
writeCmd(cmd);
cmd = "\033[~"; //del2
writeCmd(cmd);
cmd = "\033[9"; //tab
writeCmd(cmd);
cmd = "\033[127"; //backspace
writeCmd(cmd);
cmd = "\033[B"; //down
writeCmd(cmd);
std::cout << readPipeOutput() << std::endl;
}
TEST_F(ShellTuiTestSuite, WriteMoreThanMaxHistoryLines) {
createFrameworkWithShellBundles();
//WRITE more than hist max lines
int maxHist = CELIX_SHELL_TUI_HIST_MAX;
for (int i = 0; i < maxHist + 1; ++i) {
writeCmd("lb\n");
}
}