Skip to content

Commit

Permalink
Port embedSdf script from Ruby to Python3 and provide unittests (#884)
Browse files Browse the repository at this point in the history
Converts the script that creates the .cc file for mapping of SDF filename and SDF content from Ruby to Python. Additionally a few unittests are added for the function that uses the mapping and which wasn't tested before.

Signed-off-by: Bi0T1N <[email protected]>
Co-authored-by: Bi0T1N <[email protected]>
Co-authored-by: Michael Carroll <[email protected]>
  • Loading branch information
3 people committed Feb 15, 2023
1 parent f1d13b4 commit 74ef6c0
Show file tree
Hide file tree
Showing 4 changed files with 212 additions and 46 deletions.
6 changes: 5 additions & 1 deletion sdf/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,14 @@ add_subdirectory(1.10)
add_custom_target(schema)
add_dependencies(schema schema1_10)

if (NOT Python3_Interpreter_FOUND)
gz_build_error("Python is required to generate the C++ file with the SDF content")
endif()

# Generate the EmbeddedSdf.cc file, which contains all the supported SDF
# descriptions in a map of strings. The parser.cc file uses EmbeddedSdf.hh.
execute_process(
COMMAND ${RUBY} ${CMAKE_SOURCE_DIR}/sdf/embedSdf.rb
COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/sdf/embedSdf.py
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/sdf"
OUTPUT_FILE "${PROJECT_BINARY_DIR}/src/EmbeddedSdf.cc"
)
Expand Down
151 changes: 151 additions & 0 deletions sdf/embedSdf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#!/usr/bin/env python3
import inspect
from pathlib import Path, PurePosixPath

""""Script for generating a C++ file that contains the content from all SDF files"""

# The list of supported SDF specification versions. This will let us drop
# versions without removing the directories.
SUPPORTED_SDF_VERSIONS = ['1.10', '1.9', '1.8', '1.7', '1.6', '1.5', '1.4', '1.3', '1.2']

# The list of supported SDF conversions. This list includes versions that
# a user can convert an existing SDF version to.
SUPPORTED_SDF_CONVERSIONS = ['1.10', '1.9', '1.8', '1.7', '1.6', '1.5', '1.4', '1.3']

# whitespace indentation for C++ code
INDENTATION = ' '
# newline character
NEWLINE = '\n'

def get_copyright_notice() -> str:
"""
Provides the copyrigt notice for the C++ file
:returns: copyright notice
"""
res = inspect.cleandoc("""
/*
* Copyright 2022 Open Source Robotics Foundation
*
* Licensed 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.
*
*/
""")
return res + 2*NEWLINE


def get_file_header_prolog() -> str:
"""
Provides the include statement, namespace and variable declaration of the C++ file
:returns: prolog of the C++ file
"""
res = inspect.cleandoc("""
#include "EmbeddedSdf.hh"
namespace sdf
{
inline namespace SDF_VERSION_NAMESPACE
{
/////////////////////////////////////////////////
const std::map<std::string, std::string> &GetEmbeddedSdf()
{
static const std::map<std::string, std::string> result {
""")
return res + NEWLINE


def embed_sdf_content(arg_path: str, arg_file_content: str) -> str:
"""
Generates a string pair with the folder and filename as well as the content of the file
:param arg_path: Foldername and filename of the SDF
:param arg_file_content: Content of the provided file
:returns: raw string literal mapping pair for the std::map
"""
res = []
res.append('// NOLINT')
res.append('{')
res.append(f'"{arg_path}",')
res.append('R"__sdf_literal__(')
res.append(f'{arg_file_content}')
res.append(')__sdf_literal__"')
res.append('},')
res.append('')
return NEWLINE.join(res)


def get_map_content(arg_pathlist: Path) -> str:
"""
Generates a string pair with the folder and filename as well as the content
of the file in ascending order
:param arg_pathlist: Foldername and all filenames inside it
:returns: mapping pairs for the std::map
"""
map_str = ''
files = []
for path in arg_pathlist:
# dir separator is hardcoded to '/' in C++ mapping
posix_path = PurePosixPath(path)
files.append(str(posix_path))
# get ascending order
files.sort()
for file in files:
with Path(file).open() as f:
content = f.read()
map_str += embed_sdf_content(file, content)
return map_str


def get_file_header_epilog() -> str:
"""
Provides the return statement and the closing brackets of the C++ file
:returns: epilog of the C++ file
"""
res = inspect.cleandoc("""
};
return result;
}
}
} // namespace sdf
""")
return NEWLINE + res


if __name__ == "__main__":
copyright = get_copyright_notice()
prolog = get_file_header_prolog()

map_str = ""
for sdf_version in SUPPORTED_SDF_VERSIONS:
pathlist = Path(sdf_version).glob('*.sdf')
map_str += get_map_content(pathlist)

for sdf_conversion in SUPPORTED_SDF_CONVERSIONS:
pathlist = Path(sdf_conversion).glob('*.convert')
map_str += get_map_content(pathlist)

# remove the last comma
map_str = map_str[:-2]

epilog = get_file_header_epilog()

output = copyright + prolog + map_str + epilog

# output to stdin so that CMake can read it and create the appropriate file
print(output)
45 changes: 0 additions & 45 deletions sdf/embedSdf.rb

This file was deleted.

56 changes: 56 additions & 0 deletions src/SDF_TEST.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
#include <gz/utils/Environment.hh>
#include <gz/utils/SuppressWarning.hh>

#include "test_config.hh"
#include "test_utils.hh"

#include "sdf/sdf.hh"

class SDFUpdateFixture
Expand Down Expand Up @@ -559,6 +562,59 @@ TEST(SDF, Version)
EXPECT_STREQ(SDF_VERSION, sdf::SDF::Version().c_str());
}

/////////////////////////////////////////////////
TEST(SDF, EmbeddedSpec)
{
std::string result;

result = sdf::SDF::EmbeddedSpec("actor.sdf", false);
EXPECT_NE(result.find("<!-- Actor -->"), std::string::npos);
EXPECT_NE(result.find("<element name=\"actor\" required=\"*\">"),
std::string::npos);
result = sdf::SDF::EmbeddedSpec("actor.sdf", true);
EXPECT_NE(result.find("<!-- Actor -->"), std::string::npos);
EXPECT_NE(result.find("<element name=\"actor\" required=\"*\">"),
std::string::npos);

result = sdf::SDF::EmbeddedSpec("root.sdf", false);
EXPECT_NE(result.find("SDFormat base element"), std::string::npos);
EXPECT_NE(result.find("name=\"version\" type=\"string\""), std::string::npos);
result = sdf::SDF::EmbeddedSpec("root.sdf", true);
EXPECT_NE(result.find("SDFormat base element"), std::string::npos);
EXPECT_NE(result.find("name=\"version\" type=\"string\""), std::string::npos);
}

TEST(SDF, EmbeddedSpecNonExistent)
{
std::string result;

// Capture sdferr output
std::stringstream stderr_buffer;
sdf::testing::RedirectConsoleStream redir(
sdf::Console::Instance()->GetMsgStream(), &stderr_buffer);
#ifdef _WIN32
sdf::Console::Instance()->SetQuiet(false);
sdf::testing::ScopeExit revertSetQuiet(
[]
{
sdf::Console::Instance()->SetQuiet(true);
});
#endif

result = sdf::SDF::EmbeddedSpec("unavailable.sdf", false);
EXPECT_NE(stderr_buffer.str().find("Unable to find SDF filename"),
std::string::npos);
EXPECT_NE(stderr_buffer.str().find("with version"), std::string::npos);
EXPECT_TRUE(result.empty());

// clear the contents of the buffer
stderr_buffer.str("");

result = sdf::SDF::EmbeddedSpec("unavailable.sdf", true);
EXPECT_TRUE(stderr_buffer.str().empty());
EXPECT_TRUE(result.empty());
}

/////////////////////////////////////////////////
TEST(SDF, FilePath)
{
Expand Down

0 comments on commit 74ef6c0

Please sign in to comment.