Commit 9f227007 authored by Cedric Caffy's avatar Cedric Caffy
Browse files

Added JSONC Parser and stdin input passing to a program ran by Subprocess

parent fbeedb47
......@@ -20,6 +20,7 @@ find_package (binutils REQUIRED)
find_package (libattr REQUIRED)
find_package (libcap REQUIRED)
find_package (libuuid REQUIRED)
find_package(json-c REQUIRED)
add_subdirectory (exception)
......@@ -104,6 +105,7 @@ set (COMMON_LIB_SRC_FILES
exception/TimeOut.cpp
exception/UserError.cpp
exception/XrootCl.cpp
json/parser/JSONCParser.cpp
log/DummyLogger.cpp
log/FileLogger.cpp
log/LogContext.cpp
......@@ -166,6 +168,7 @@ target_link_libraries (ctacommon
z
cap
XrdCl
json-c
)
set (COMMON_UNIT_TESTS_LIB_SRC_FILES
......@@ -200,7 +203,9 @@ set (COMMON_UNIT_TESTS_LIB_SRC_FILES
utils/RegexTest.cpp
utils/UtilsTest.cpp
optionalTest.cpp
rangeTest.cpp)
rangeTest.cpp
json/parser/JSONCParserTest.cpp
)
add_library (ctacommonunittests SHARED
${COMMON_UNIT_TESTS_LIB_SRC_FILES})
......
/*
* The CERN Tape Archive (CTA) project
* Copyright (C) 2019 CERN
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <string>
namespace cta { namespace utils { namespace json { namespace object {
struct SchedulerHints {
int test;
std::string test2;
};
}}}}
/*
* The CERN Tape Archive (CTA) project
* Copyright (C) 2019 CERN
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <string>
namespace cta { namespace utils { namespace json { namespace object {
struct TestObject {
uint64_t integer_number;
std::string str;
double double_number;
};
}}}}
\ No newline at end of file
/*
* The CERN Tape Archive (CTA) project
* Copyright (C) 2019 CERN
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <json-c/json.h>
#include <json/json_object.h>
#include "JSONCParser.hpp"
#include "common/json/object/SchedulerHints.hpp"
#include "common/json/object/TestObject.hpp"
#include "common/exception/Exception.hpp"
#include "JSONParserException.hpp"
namespace cta { namespace utils { namespace json { namespace parser {
JSONCParser::JSONCParser() {
m_jsonObject = json_object_new_object();
}
void JSONCParser::setJSONToBeParsed(const std::string& json){
//DO JSON_C deinitialization
if(m_jsonObject != nullptr){
json_object_put(m_jsonObject);
m_jsonObject = nullptr;
}
m_jsonObject = json_tokener_parse(json.c_str());
}
std::string JSONCParser::getJSON() const {
return std::string(json_object_to_json_string_ext(m_jsonObject, JSON_C_TO_STRING_PLAIN));
}
JSONCParser::~JSONCParser() {
//Free the JSON object if initialized
if(m_jsonObject != nullptr){
json_object_put(m_jsonObject);
m_jsonObject = nullptr;
}
}
json_object * JSONCParser::getJSONObject(const std::string& key){
json_object * objectRet;
if(json_object_object_get_ex(m_jsonObject,key.c_str(),&objectRet)){
return objectRet;
}
std::string errMsg = "In JSONCParser::getJSONObject(), the provided json does not contain any key named \""+key+"\".";
throw cta::exception::JSONParserException(errMsg);
}
////////////////////////////////////////////////////////////////////
// JSONCParser::getValue() implementation START
////////////////////////////////////////////////////////////////////
template<>
std::string JSONCParser::getValue(const std::string& key){
json_object * jsonObj = getJSONObject(key);
return std::string(json_object_get_string(jsonObj));
}
template<>
uint64_t JSONCParser::getValue(const std::string & key){
json_object * jsonObj = getJSONObject(key);
return json_object_get_int64(jsonObj);
}
template<>
double JSONCParser::getValue(const std::string & key){
json_object * jsonObj = getJSONObject(key);
return json_object_get_double(jsonObj);
}
////////////////////////////////////////////////////////////////////
// JSONCParser::getValue() implementation END
////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////
// json::object::TestObject implementation START
////////////////////////////////////////////////////////////////////
template<>
void JSONCParser::generateJSONFromObject(const object::TestObject & value){
json_object_object_add(m_jsonObject,"integer_number",json_object_new_int64(value.integer_number));
json_object_object_add(m_jsonObject,"str",json_object_new_string(value.str.c_str()));
json_object_object_add(m_jsonObject,"double_number",json_object_new_double(value.double_number));
}
template<>
object::TestObject JSONCParser::getObjectFromJSON(){
object::TestObject ret;
ret.str = getValue<std::string>("str");
ret.integer_number = getValue<uint64_t>("integer_number");
ret.double_number = getValue<double>("double_number");
return ret;
}
////////////////////////////////////////////////////////////////////
// json::object::TestObject implementation END
////////////////////////////////////////////////////////////////////
template <>
void JSONCParser::generateJSONFromObject(const object::SchedulerHints & value){
}
}}}}
\ No newline at end of file
/*
* The CERN Tape Archive (CTA) project
* Copyright (C) 2019 CERN
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <string>
#include <json-c/json.h>
#include <memory>
namespace cta { namespace utils { namespace json { namespace parser {
class JSONCParser {
public:
JSONCParser();
void setJSONToBeParsed(const std::string & json);
std::string getJSON() const;
template<typename T>
void generateJSONFromObject(const T & value);
template<typename T>
T getObjectFromJSON();
virtual ~JSONCParser();
private:
json_object * m_jsonObject = nullptr;
template<typename T>
T getValue(const std::string & key);
json_object * getJSONObject(const std::string & key);
};
}}}}
/*
* The CERN Tape Archive (CTA) project
* Copyright (C) 2019 CERN
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <gtest/gtest.h>
#include "common/json/parser/JSONCParser.hpp"
#include "common/json/object/TestObject.hpp"
#include "JSONParserException.hpp"
namespace unitTests {
using namespace cta::utils;
TEST(JSONCParserTest, testJSONGenerationFromObject) {
json::object::TestObject to;
to.double_number = 42.0;
to.integer_number = 42;
to.str = "forty two";
json::parser::JSONCParser parser;
parser.generateJSONFromObject(to);
ASSERT_EQ("{\"integer_number\":42,\"str\":\"forty two\",\"double_number\":42.000000}",parser.getJSON());
}
TEST(JSONCParserTest, testObjectGenerationFromJSON){
std::string json = "{\"integer_number\":42,\"str\":\"forty two\",\"double_number\":42.000000}";
json::parser::JSONCParser parser;
parser.setJSONToBeParsed(json);
json::object::TestObject to = parser.getObjectFromJSON<decltype(to)>();
ASSERT_EQ(42,to.integer_number);
ASSERT_EQ("forty two",to.str);
ASSERT_EQ(42.000000,to.double_number);
}
TEST(JSONCParserTest, testJSONCParserGetObjectFromUninitializedJSON){
json::parser::JSONCParser parser;
ASSERT_THROW(parser.getObjectFromJSON<json::object::TestObject>(),cta::exception::JSONParserException);
}
TEST(JSONCParserTest, testJSONCParserGetJSONShouldReturnEmptyJSON){
json::parser::JSONCParser parser;
ASSERT_EQ("{}",parser.getJSON());
}
TEST(JSONCParserTest, testJSONCParserSetJSONToBeParsedWrongJSONFormat){
json::parser::JSONCParser parser;
parser.setJSONToBeParsed("WRONG_JSON_STRING");
ASSERT_EQ("null",parser.getJSON());
parser.setJSONToBeParsed("{\"test");
ASSERT_EQ("null",parser.getJSON());
ASSERT_THROW(parser.getObjectFromJSON<json::object::TestObject>(),cta::exception::JSONParserException);
}
}
\ No newline at end of file
/*
* The CERN Tape Archive (CTA) project
* Copyright (C) 2019 CERN
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "common/exception/Exception.hpp"
namespace cta { namespace exception {
class JSONParserException : public Exception {
using Exception::Exception;
};
}}
\ No newline at end of file
......@@ -63,7 +63,7 @@ private:
}
namespace cta { namespace threading {
SubProcess::SubProcess(const std::string & executable, const std::list<std::string>& argv):
SubProcess::SubProcess(const std::string & executable, const std::list<std::string>& argv, const std::string & stdinInput):
m_childComplete(false) {
// Sanity checks
if (argv.size() < 1)
......@@ -74,10 +74,13 @@ m_childComplete(false) {
const size_t writeSide=1;
int stdoutPipe[2];
int stderrPipe[2];
int stdinPipe[2];
cta::exception::Errnum::throwOnNonZero(::pipe2(stdoutPipe, O_NONBLOCK),
"In Subprocess::Subprocess failed to create the stdout pipe");
cta::exception::Errnum::throwOnNonZero(::pipe2(stderrPipe, O_NONBLOCK),
"In Subprocess::Subprocess failed to create the stderr pipe");
cta::exception::Errnum::throwOnNonZero(::pipe2(stdinPipe,O_NONBLOCK),
"In Subprocess::Subprocess failed to create the stdin pipe");
// Prepare the actions to be taken on file descriptors
ScopedPosixSpawnFileActions fileActions;
// We will be the child process. Close the read sides of the pipes.
......@@ -85,16 +88,25 @@ m_childComplete(false) {
"In Subprocess::Subprocess(): failed to posix_spawn_file_actions_addclose() (1)");
cta::exception::Errnum::throwOnReturnedErrno(posix_spawn_file_actions_addclose(fileActions, stderrPipe[readSide]),
"In Subprocess::Subprocess(): failed to posix_spawn_file_actions_addclose() (2)");
// Close stdin and rewire the stdout and stderr to the pipes.
// We close the write side of the stdinPipe: the child does not write in it
cta::exception::Errnum::throwOnReturnedErrno(posix_spawn_file_actions_addclose(fileActions, stdinPipe[writeSide]),
"In Subprocess::Subprocess(): failed to posix_spawn_file_actions_addclose() (3)");
//Rewire the stdout and stderr to the pipes.
cta::exception::Errnum::throwOnReturnedErrno(posix_spawn_file_actions_adddup2(fileActions, stdoutPipe[writeSide], STDOUT_FILENO),
"In Subprocess::Subprocess(): failed to posix_spawn_file_actions_adddup2() (1)");
cta::exception::Errnum::throwOnReturnedErrno(posix_spawn_file_actions_adddup2(fileActions, stderrPipe[writeSide], STDERR_FILENO),
"In Subprocess::Subprocess(): failed to posix_spawn_file_actions_adddup2() (2)");
//Rewiring the read side of the stdin pipe to stdin
cta::exception::Errnum::throwOnReturnedErrno(posix_spawn_file_actions_adddup2(fileActions, stdinPipe[readSide], STDIN_FILENO),
"In Subprocess::Subprocess(): failed to posix_spawn_file_actions_adddup2() (3)");
// Close the now duplicated pipe file descriptors
cta::exception::Errnum::throwOnReturnedErrno(posix_spawn_file_actions_addclose(fileActions, stdoutPipe[writeSide]),
"In Subprocess::Subprocess(): failed to posix_spawn_file_actions_addclose() (3)");
cta::exception::Errnum::throwOnReturnedErrno(posix_spawn_file_actions_addclose(fileActions, stderrPipe[writeSide]),
"In Subprocess::Subprocess(): failed to posix_spawn_file_actions_addclose() (4)");
cta::exception::Errnum::throwOnReturnedErrno(posix_spawn_file_actions_addclose(fileActions, stderrPipe[writeSide]),
"In Subprocess::Subprocess(): failed to posix_spawn_file_actions_addclose() (5)");
cta::exception::Errnum::throwOnReturnedErrno(posix_spawn_file_actions_addclose(fileActions, stdinPipe[readSide]),
"In Subprocess::Subprocess(): failed to posix_spawn_file_actions_addclose() (6)");
// And finally spawn the subprocess
// Prepare the spawn attributes (we need vfork)
ScopedPosixSpawnAttr attr;
......@@ -114,7 +126,15 @@ m_childComplete(false) {
int spawnRc=::posix_spawnp(&m_child, executable.c_str(), fileActions, attr, cargv.get(), ::environ);
cta::exception::Errnum::throwOnReturnedErrno(spawnRc, "In Subprocess::Subprocess failed to posix_spawn()");
}
// We are the parent process. Close the write sides of pipes.
// We are the parent process.
// close the readSide of stdin pipe as we are going to write to the subprocess stdin
//Send input to child stdin
//Write data to child stdin
::write(stdinPipe[writeSide],stdinInput.c_str(),stdinInput.size());
//Close stdin
::close(stdinPipe[writeSide]);
::close(stdinPipe[readSide]);
//Close the write sides of pipes.
::close(stdoutPipe[writeSide]);
::close(stderrPipe[writeSide]);
m_stdoutFd = stdoutPipe[readSide];
......
......@@ -33,7 +33,7 @@ namespace cta { namespace threading {
*/
class SubProcess {
public:
SubProcess(const std::string & program, const std::list<std::string> &argv);
SubProcess(const std::string & program, const std::list<std::string> &argv, const std::string & str = "");
~SubProcess();
void wait(void);
std::string stdout();
......
......@@ -38,4 +38,13 @@ TEST(SubProcessHelper, basicTests) {
ASSERT_EQ(127, sp3.exitValue());
ASSERT_EQ("", sp3.stderr());
}
TEST(SubProcessHelper, testSubprocessWithStdinInput) {
std::string stdinInput = "{\"integer_number\":42,\"str\":\"forty two\",\"double_number\":42.000000}";
cta::threading::SubProcess sp2("tee", std::list<std::string>({"tee"}),stdinInput);
sp2.wait();
ASSERT_EQ(stdinInput, sp2.stdout());
ASSERT_EQ(0, sp2.exitValue());
ASSERT_EQ("", sp2.stderr());
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment