style: apply black and isort

This commit is contained in:
Gabriel Ferreira
2023-11-19 20:07:19 -03:00
parent af98671fbe
commit 8f6a3413a4
68 changed files with 7848 additions and 4299 deletions

View File

@@ -42,7 +42,6 @@ import re
import shutil
import subprocess
import sys
from typing import Callable, Dict, List, Tuple
###########################################################
@@ -55,76 +54,76 @@ CLANG_FORMAT_VERSIONS = [
14,
]
CLANG_FORMAT_GUARD_ON = '// clang-format on'
CLANG_FORMAT_GUARD_OFF = '// clang-format off'
CLANG_FORMAT_GUARD_ON = "// clang-format on"
CLANG_FORMAT_GUARD_OFF = "// clang-format off"
DIRECTORIES_TO_SKIP = [
'__pycache__',
'.git',
'bindings',
'build',
'cmake-cache',
'testpy-output',
"__pycache__",
".git",
"bindings",
"build",
"cmake-cache",
"testpy-output",
]
# List of files entirely copied from elsewhere that should not be checked,
# in order to optimize the performance of this script
FILES_TO_SKIP = [
'valgrind.h',
"valgrind.h",
]
FILE_EXTENSIONS_TO_CHECK_FORMATTING = [
'.c',
'.cc',
'.h',
".c",
".cc",
".h",
]
FILE_EXTENSIONS_TO_CHECK_INCLUDE_PREFIXES = FILE_EXTENSIONS_TO_CHECK_FORMATTING
FILE_EXTENSIONS_TO_CHECK_WHITESPACE = [
'.c',
'.cc',
'.click',
'.cmake',
'.conf',
'.css',
'.dot',
'.gnuplot',
'.gp',
'.h',
'.html',
'.js',
'.json',
'.m',
'.md',
'.mob',
'.ns_params',
'.ns_movements',
'.params',
'.pl',
'.plt',
'.py',
'.rst',
'.seqdiag',
'.sh',
'.txt',
'.yml',
".c",
".cc",
".click",
".cmake",
".conf",
".css",
".dot",
".gnuplot",
".gp",
".h",
".html",
".js",
".json",
".m",
".md",
".mob",
".ns_params",
".ns_movements",
".params",
".pl",
".plt",
".py",
".rst",
".seqdiag",
".sh",
".txt",
".yml",
]
FILES_TO_CHECK_WHITESPACE = [
'Makefile',
'ns3',
"Makefile",
"ns3",
]
FILE_EXTENSIONS_TO_CHECK_TABS = [
'.c',
'.cc',
'.h',
'.md',
'.py',
'.rst',
'.sh',
'.yml',
".c",
".cc",
".h",
".md",
".py",
".rst",
".sh",
".yml",
]
TAB_SIZE = 4
@@ -142,14 +141,16 @@ def should_analyze_directory(dirpath: str) -> bool:
_, directory = os.path.split(dirpath)
return not (directory in DIRECTORIES_TO_SKIP or
(directory.startswith('.') and directory != '.'))
return not (
directory in DIRECTORIES_TO_SKIP or (directory.startswith(".") and directory != ".")
)
def should_analyze_file(path: str,
files_to_check: List[str],
file_extensions_to_check: List[str],
) -> bool:
def should_analyze_file(
path: str,
files_to_check: List[str],
file_extensions_to_check: List[str],
) -> bool:
"""
Check whether a file should be analyzed.
@@ -166,11 +167,12 @@ def should_analyze_file(path: str,
basename, extension = os.path.splitext(filename)
return (basename in files_to_check or
extension in file_extensions_to_check)
return basename in files_to_check or extension in file_extensions_to_check
def find_files_to_check_style(paths: List[str]) -> Tuple[List[str], List[str], List[str], List[str]]:
def find_files_to_check_style(
paths: List[str],
) -> Tuple[List[str], List[str], List[str], List[str]]:
"""
Find all files to be checked in a given list of paths.
@@ -199,7 +201,7 @@ def find_files_to_check_style(paths: List[str]) -> Tuple[List[str], List[str], L
files_to_check.extend([os.path.join(dirpath, f) for f in filenames])
else:
raise ValueError(f'Error: {path} is not a file nor a directory')
raise ValueError(f"Error: {path} is not a file nor a directory")
files_to_check.sort()
@@ -239,47 +241,48 @@ def find_clang_format_path() -> str:
# Find exact version
for version in CLANG_FORMAT_VERSIONS:
clang_format_path = shutil.which(f'clang-format-{version}')
clang_format_path = shutil.which(f"clang-format-{version}")
if clang_format_path:
return clang_format_path
# Find default version and check if it is supported
clang_format_path = shutil.which('clang-format')
clang_format_path = shutil.which("clang-format")
if clang_format_path:
process = subprocess.run(
[clang_format_path, '--version'],
[clang_format_path, "--version"],
capture_output=True,
text=True,
check=True,
)
version = process.stdout.strip().split(' ')[-1]
major_version = int(version.split('.')[0])
version = process.stdout.strip().split(" ")[-1]
major_version = int(version.split(".")[0])
if major_version in CLANG_FORMAT_VERSIONS:
return clang_format_path
# No supported version of clang-format found
raise RuntimeError(
f'Could not find any supported version of clang-format installed on this system. '
f'List of supported versions: {CLANG_FORMAT_VERSIONS}.'
f"Could not find any supported version of clang-format installed on this system. "
f"List of supported versions: {CLANG_FORMAT_VERSIONS}."
)
###########################################################
# CHECK STYLE MAIN FUNCTIONS
###########################################################
def check_style_clang_format(paths: List[str],
enable_check_include_prefixes: bool,
enable_check_formatting: bool,
enable_check_whitespace: bool,
enable_check_tabs: bool,
fix: bool,
verbose: bool,
n_jobs: int = 1,
) -> bool:
def check_style_clang_format(
paths: List[str],
enable_check_include_prefixes: bool,
enable_check_formatting: bool,
enable_check_whitespace: bool,
enable_check_tabs: bool,
fix: bool,
verbose: bool,
n_jobs: int = 1,
) -> bool:
"""
Check / fix the coding style of a list of files.
@@ -294,10 +297,12 @@ def check_style_clang_format(paths: List[str],
@return Whether all files are compliant with all enabled style checks.
"""
(files_to_check_include_prefixes,
files_to_check_formatting,
files_to_check_whitespace,
files_to_check_tabs) = find_files_to_check_style(paths)
(
files_to_check_include_prefixes,
files_to_check_formatting,
files_to_check_whitespace,
files_to_check_tabs,
) = find_files_to_check_style(paths)
check_include_prefixes_successful = True
check_formatting_successful = True
@@ -316,11 +321,11 @@ def check_style_clang_format(paths: List[str],
check_style_line_function=check_include_prefixes_line,
)
print('')
print("")
if enable_check_formatting:
check_formatting_successful = check_style_files(
'bad code formatting',
"bad code formatting",
check_formatting_file,
files_to_check_formatting,
fix,
@@ -329,11 +334,11 @@ def check_style_clang_format(paths: List[str],
clang_format_path=find_clang_format_path(),
)
print('')
print("")
if enable_check_whitespace:
check_whitespace_successful = check_style_files(
'trailing whitespace',
"trailing whitespace",
check_manually_file,
files_to_check_whitespace,
fix,
@@ -343,11 +348,11 @@ def check_style_clang_format(paths: List[str],
check_style_line_function=check_whitespace_line,
)
print('')
print("")
if enable_check_tabs:
check_tabs_successful = check_style_files(
'tabs',
"tabs",
check_manually_file,
files_to_check_tabs,
fix,
@@ -357,22 +362,25 @@ def check_style_clang_format(paths: List[str],
check_style_line_function=check_tabs_line,
)
return all([
check_include_prefixes_successful,
check_formatting_successful,
check_whitespace_successful,
check_tabs_successful,
])
return all(
[
check_include_prefixes_successful,
check_formatting_successful,
check_whitespace_successful,
check_tabs_successful,
]
)
def check_style_files(style_check_str: str,
check_style_file_function: Callable[..., Tuple[str, bool, List[str]]],
filenames: List[str],
fix: bool,
verbose: bool,
n_jobs: int,
**kwargs,
) -> bool:
def check_style_files(
style_check_str: str,
check_style_file_function: Callable[..., Tuple[str, bool, List[str]]],
filenames: List[str],
fix: bool,
verbose: bool,
n_jobs: int,
**kwargs,
) -> bool:
"""
Check / fix style of a list of files.
@@ -399,7 +407,7 @@ def check_style_files(style_check_str: str,
*[arg if isinstance(arg, list) else itertools.repeat(arg) for arg in kwargs.values()],
)
for (filename, is_file_compliant, verbose_infos) in non_compliant_files_results:
for filename, is_file_compliant, verbose_infos in non_compliant_files_results:
if not is_file_compliant:
non_compliant_files.append(filename)
@@ -408,22 +416,22 @@ def check_style_files(style_check_str: str,
# Output results
if not non_compliant_files:
print(f'- No files detected with {style_check_str}')
print(f"- No files detected with {style_check_str}")
return True
else:
n_non_compliant_files = len(non_compliant_files)
if fix:
print(f'- Fixed {style_check_str} in the files ({n_non_compliant_files}):')
print(f"- Fixed {style_check_str} in the files ({n_non_compliant_files}):")
else:
print(f'- Detected {style_check_str} in the files ({n_non_compliant_files}):')
print(f"- Detected {style_check_str} in the files ({n_non_compliant_files}):")
for f in non_compliant_files:
if verbose:
print(*[f' {l}' for l in files_verbose_infos[f]], sep='\n')
print(*[f" {l}" for l in files_verbose_infos[f]], sep="\n")
else:
print(f' - {f}')
print(f" - {f}")
# If all files were fixed, there are no more non-compliant files
return fix
@@ -432,11 +440,12 @@ def check_style_files(style_check_str: str,
###########################################################
# CHECK STYLE FUNCTIONS
###########################################################
def check_formatting_file(filename: str,
fix: bool,
verbose: bool,
clang_format_path: str,
) -> Tuple[str, bool, List[str]]:
def check_formatting_file(
filename: str,
fix: bool,
verbose: bool,
clang_format_path: str,
) -> Tuple[str, bool, List[str]]:
"""
Check / fix the coding style of a file with clang-format.
@@ -456,18 +465,18 @@ def check_formatting_file(filename: str,
[
clang_format_path,
filename,
'-style=file',
'--dry-run',
'--Werror',
"-style=file",
"--dry-run",
"--Werror",
# Optimization: In non-verbose mode, only one error is needed to check that the file is not compliant
f'--ferror-limit={0 if verbose else 1}',
f"--ferror-limit={0 if verbose else 1}",
],
check=False,
capture_output=True,
text=True,
)
is_file_compliant = (process.returncode == 0)
is_file_compliant = process.returncode == 0
if verbose:
verbose_infos = process.stderr.splitlines()
@@ -478,8 +487,8 @@ def check_formatting_file(filename: str,
[
clang_format_path,
filename,
'-style=file',
'-i',
"-style=file",
"-i",
],
check=False,
stdout=subprocess.DEVNULL,
@@ -489,12 +498,13 @@ def check_formatting_file(filename: str,
return (filename, is_file_compliant, verbose_infos)
def check_manually_file(filename: str,
fix: bool,
verbose: bool,
respect_clang_format_guards: bool,
check_style_line_function: Callable[[str, str, int], Tuple[bool, str, List[str]]],
) -> Tuple[str, bool, List[str]]:
def check_manually_file(
filename: str,
fix: bool,
verbose: bool,
respect_clang_format_guards: bool,
check_style_line_function: Callable[[str, str, int], Tuple[bool, str, List[str]]],
) -> Tuple[str, bool, List[str]]:
"""
Check / fix a file manually using a function to check / fix each line.
@@ -512,11 +522,10 @@ def check_manually_file(filename: str,
verbose_infos: List[str] = []
clang_format_enabled = True
with open(filename, 'r', encoding='utf-8') as f:
with open(filename, "r", encoding="utf-8") as f:
file_lines = f.readlines()
for (i, line) in enumerate(file_lines):
for i, line in enumerate(file_lines):
# Check clang-format guards
if respect_clang_format_guards:
line_stripped = line.strip()
@@ -526,12 +535,16 @@ def check_manually_file(filename: str,
elif line_stripped == CLANG_FORMAT_GUARD_OFF:
clang_format_enabled = False
if (not clang_format_enabled and
line_stripped not in (CLANG_FORMAT_GUARD_ON, CLANG_FORMAT_GUARD_OFF)):
if not clang_format_enabled and line_stripped not in (
CLANG_FORMAT_GUARD_ON,
CLANG_FORMAT_GUARD_OFF,
):
continue
# Check if the line is compliant with the style and fix it
(is_line_compliant, line_fixed, line_verbose_infos) = check_style_line_function(line, filename, i)
(is_line_compliant, line_fixed, line_verbose_infos) = check_style_line_function(
line, filename, i
)
if not is_line_compliant:
is_file_compliant = False
@@ -544,16 +557,17 @@ def check_manually_file(filename: str,
# Update file with the fixed lines
if fix and not is_file_compliant:
with open(filename, 'w', encoding='utf-8') as f:
with open(filename, "w", encoding="utf-8") as f:
f.writelines(file_lines)
return (filename, is_file_compliant, verbose_infos)
def check_include_prefixes_line(line: str,
filename: str,
line_number: int,
) -> Tuple[bool, str, List[str]]:
def check_include_prefixes_line(
line: str,
filename: str,
line_number: int,
) -> Tuple[bool, str, List[str]]:
"""
Check / fix #include headers from the same module with the "ns3/" prefix in a line.
@@ -580,24 +594,31 @@ def check_include_prefixes_line(line: str,
if os.path.exists(os.path.join(parent_path, header_file)):
is_line_compliant = False
line_fixed = line_stripped.replace(
f'ns3/{header_file}', header_file).replace('<', '"').replace('>', '"') + '\n'
line_fixed = (
line_stripped.replace(f"ns3/{header_file}", header_file)
.replace("<", '"')
.replace(">", '"')
+ "\n"
)
header_index = len('#include "')
verbose_infos.extend([
f'{filename}:{line_number + 1}:{header_index + 1}: error: #include headers from the same module with the "ns3/" prefix detected',
f' {line_stripped}',
f' {"":{header_index}}^',
])
verbose_infos.extend(
[
f'{filename}:{line_number + 1}:{header_index + 1}: error: #include headers from the same module with the "ns3/" prefix detected',
f" {line_stripped}",
f' {"":{header_index}}^',
]
)
return (is_line_compliant, line_fixed, verbose_infos)
def check_whitespace_line(line: str,
filename: str,
line_number: int,
) -> Tuple[bool, str, List[str]]:
def check_whitespace_line(
line: str,
filename: str,
line_number: int,
) -> Tuple[bool, str, List[str]]:
"""
Check / fix whitespace in a line.
@@ -610,7 +631,7 @@ def check_whitespace_line(line: str,
"""
is_line_compliant = True
line_fixed = line.rstrip() + '\n'
line_fixed = line.rstrip() + "\n"
verbose_infos: List[str] = []
if line_fixed != line:
@@ -618,18 +639,19 @@ def check_whitespace_line(line: str,
line_fixed_stripped_expanded = line_fixed.rstrip().expandtabs(TAB_SIZE)
verbose_infos = [
f'{filename}:{line_number + 1}:{len(line_fixed_stripped_expanded) + 1}: error: Trailing whitespace detected',
f' {line_fixed_stripped_expanded}',
f"{filename}:{line_number + 1}:{len(line_fixed_stripped_expanded) + 1}: error: Trailing whitespace detected",
f" {line_fixed_stripped_expanded}",
f' {"":{len(line_fixed_stripped_expanded)}}^',
]
return (is_line_compliant, line_fixed, verbose_infos)
def check_tabs_line(line: str,
filename: str,
line_number: int,
) -> Tuple[bool, str, List[str]]:
def check_tabs_line(
line: str,
filename: str,
line_number: int,
) -> Tuple[bool, str, List[str]]:
"""
Check / fix tabs in a line.
@@ -645,15 +667,15 @@ def check_tabs_line(line: str,
line_fixed = line
verbose_infos: List[str] = []
tab_index = line.find('\t')
tab_index = line.find("\t")
if tab_index != -1:
is_line_compliant = False
line_fixed = line.expandtabs(TAB_SIZE)
verbose_infos = [
f'{filename}:{line_number + 1}:{tab_index + 1}: error: Tab detected',
f' {line.rstrip()}',
f"{filename}:{line_number + 1}:{tab_index + 1}: error: Tab detected",
f" {line.rstrip()}",
f' {"":{tab_index}}^',
]
@@ -663,42 +685,71 @@ def check_tabs_line(line: str,
###########################################################
# MAIN
###########################################################
if __name__ == '__main__':
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description='Check and apply the ns-3 coding style recursively to all files in the given PATHs. '
'The script checks the formatting of the file with clang-format. '
description="Check and apply the ns-3 coding style recursively to all files in the given PATHs. "
"The script checks the formatting of the file with clang-format. "
'Additionally, it checks #include headers from the same module with the "ns3/" prefix, '
'the presence of trailing whitespace and tabs. '
"the presence of trailing whitespace and tabs. "
'Formatting, local #include "ns3/" prefixes and tabs checks respect clang-format guards. '
'When used in "check mode" (default), the script checks if all files are well '
'formatted and do not have trailing whitespace nor tabs. '
'If it detects non-formatted files, they will be printed and this process exits with a '
'non-zero code. When used in "fix mode", this script automatically fixes the files.')
"formatted and do not have trailing whitespace nor tabs. "
"If it detects non-formatted files, they will be printed and this process exits with a "
'non-zero code. When used in "fix mode", this script automatically fixes the files.'
)
parser.add_argument('paths', action='store', type=str, nargs='+',
help='List of paths to the files to check',)
parser.add_argument(
"paths",
action="store",
type=str,
nargs="+",
help="List of paths to the files to check",
)
parser.add_argument('--no-include-prefixes', action='store_true',
help='Do not check / fix #include headers from the same module with the "ns3/" prefix',)
parser.add_argument(
"--no-include-prefixes",
action="store_true",
help='Do not check / fix #include headers from the same module with the "ns3/" prefix',
)
parser.add_argument('--no-formatting', action='store_true',
help='Do not check / fix code formatting',)
parser.add_argument(
"--no-formatting",
action="store_true",
help="Do not check / fix code formatting",
)
parser.add_argument('--no-whitespace', action='store_true',
help='Do not check / fix trailing whitespace',)
parser.add_argument(
"--no-whitespace",
action="store_true",
help="Do not check / fix trailing whitespace",
)
parser.add_argument('--no-tabs', action='store_true',
help='Do not check / fix tabs',)
parser.add_argument(
"--no-tabs",
action="store_true",
help="Do not check / fix tabs",
)
parser.add_argument('--fix', action='store_true',
help='Fix coding style issues detected in the files',)
parser.add_argument(
"--fix",
action="store_true",
help="Fix coding style issues detected in the files",
)
parser.add_argument('-v', '--verbose', action='store_true',
help='Show the lines that are not well-formatted',)
parser.add_argument(
"-v",
"--verbose",
action="store_true",
help="Show the lines that are not well-formatted",
)
parser.add_argument('-j', '--jobs', type=int, default=max(1, os.cpu_count() - 1),
help='Number of parallel jobs',)
parser.add_argument(
"-j",
"--jobs",
type=int,
default=max(1, os.cpu_count() - 1),
help="Number of parallel jobs",
)
args = parser.parse_args()

View File

@@ -1,13 +1,12 @@
#! /usr/bin/env python3
import sys
import argparse
import os
import re
import shutil
import sys
from pathlib import Path
CMAKELISTS_TEMPLATE = '''\
CMAKELISTS_TEMPLATE = """\
check_include_file_cxx(stdint.h HAVE_STDINT_H)
if(HAVE_STDINT_H)
add_definitions(-DHAVE_STDINT_H)
@@ -30,10 +29,10 @@ build_lib(
TEST_SOURCES test/{MODULE}-test-suite.cc
${{examples_as_tests_sources}}
)
'''
"""
MODEL_CC_TEMPLATE = '''\
MODEL_CC_TEMPLATE = """\
#include "{MODULE}.h"
namespace ns3
@@ -42,10 +41,10 @@ namespace ns3
/* ... */
}}
'''
"""
MODEL_H_TEMPLATE = '''\
MODEL_H_TEMPLATE = """\
#ifndef {INCLUDE_GUARD}
#define {INCLUDE_GUARD}
@@ -66,10 +65,10 @@ namespace ns3
}}
#endif /* {INCLUDE_GUARD} */
'''
"""
HELPER_CC_TEMPLATE = '''\
HELPER_CC_TEMPLATE = """\
#include "{MODULE}-helper.h"
namespace ns3
@@ -78,10 +77,10 @@ namespace ns3
/* ... */
}}
'''
"""
HELPER_H_TEMPLATE = '''\
HELPER_H_TEMPLATE = """\
#ifndef {INCLUDE_GUARD}
#define {INCLUDE_GUARD}
@@ -98,18 +97,18 @@ namespace ns3
}}
#endif /* {INCLUDE_GUARD} */
'''
"""
EXAMPLES_CMAKELISTS_TEMPLATE = '''\
EXAMPLES_CMAKELISTS_TEMPLATE = """\
build_lib_example(
NAME {MODULE}-example
SOURCE_FILES {MODULE}-example.cc
LIBRARIES_TO_LINK ${{lib{MODULE}}}
)
'''
"""
EXAMPLE_CC_TEMPLATE = '''\
EXAMPLE_CC_TEMPLATE = """\
#include "ns3/core-module.h"
#include "ns3/{MODULE}-helper.h"
@@ -137,10 +136,10 @@ main(int argc, char* argv[])
Simulator::Destroy();
return 0;
}}
'''
"""
TEST_CC_TEMPLATE = '''\
TEST_CC_TEMPLATE = """\
// Include a header file from your module to test.
#include "ns3/{MODULE}.h"
@@ -227,10 +226,10 @@ class {CAPITALIZED}TestSuite : public TestSuite
* Static variable for test initialization
*/
static {CAPITALIZED}TestSuite s{COMPOUND}TestSuite;
'''
"""
DOC_RST_TEMPLATE = '''Example Module Documentation
DOC_RST_TEMPLATE = """Example Module Documentation
----------------------------
.. include:: replace.txt
@@ -328,18 +327,19 @@ Validation
Describe how the model has been tested/validated. What tests run in the
test suite? How much API and code is covered by the tests? Again,
references to outside published work may help here.
'''
"""
def create_file(path, template, **kwargs):
artifact_path = Path(path)
#open file for (w)rite and in (t)ext mode
# open file for (w)rite and in (t)ext mode
with artifact_path.open("wt", encoding="utf-8") as f:
f.write(template.format(**kwargs))
def make_cmakelists(moduledir, modname):
path = Path(moduledir, 'CMakeLists.txt')
path = Path(moduledir, "CMakeLists.txt")
macro = "build_lib"
create_file(path, CMAKELISTS_TEMPLATE, MODULE=modname)
@@ -350,14 +350,12 @@ def make_model(moduledir, modname):
modelpath = Path(moduledir, "model")
modelpath.mkdir(parents=True)
srcfile_path = modelpath.joinpath(modname).with_suffix('.cc')
srcfile_path = modelpath.joinpath(modname).with_suffix(".cc")
create_file(srcfile_path, MODEL_CC_TEMPLATE, MODULE=modname)
hfile_path = modelpath.joinpath(modname).with_suffix('.h')
guard = "{}_H".format(modname.replace('-', '_').upper())
create_file(hfile_path, MODEL_H_TEMPLATE,
MODULE=modname,
INCLUDE_GUARD=guard)
hfile_path = modelpath.joinpath(modname).with_suffix(".h")
guard = "{}_H".format(modname.replace("-", "_").upper())
create_file(hfile_path, MODEL_H_TEMPLATE, MODULE=modname, INCLUDE_GUARD=guard)
return True
@@ -366,11 +364,17 @@ def make_test(moduledir, modname):
testpath = Path(moduledir, "test")
testpath.mkdir(parents=True)
file_path = testpath.joinpath(modname+'-test-suite').with_suffix('.cc')
name_parts = modname.split('-')
create_file(file_path, TEST_CC_TEMPLATE, MODULE=modname,
CAPITALIZED=''.join([word.capitalize() for word in name_parts]),
COMPOUND=''.join([word.capitalize() if index > 0 else word for index, word in enumerate(name_parts)]))
file_path = testpath.joinpath(modname + "-test-suite").with_suffix(".cc")
name_parts = modname.split("-")
create_file(
file_path,
TEST_CC_TEMPLATE,
MODULE=modname,
CAPITALIZED="".join([word.capitalize() for word in name_parts]),
COMPOUND="".join(
[word.capitalize() if index > 0 else word for index, word in enumerate(name_parts)]
),
)
return True
@@ -379,11 +383,11 @@ def make_helper(moduledir, modname):
helperpath = Path(moduledir, "helper")
helperpath.mkdir(parents=True)
srcfile_path = helperpath.joinpath(modname+'-helper').with_suffix('.cc')
srcfile_path = helperpath.joinpath(modname + "-helper").with_suffix(".cc")
create_file(srcfile_path, HELPER_CC_TEMPLATE, MODULE=modname)
h_file_path = helperpath.joinpath(modname+'-helper').with_suffix('.h')
guard = "{}_HELPER_H".format(modname.replace('-', '_').upper())
h_file_path = helperpath.joinpath(modname + "-helper").with_suffix(".h")
guard = "{}_HELPER_H".format(modname.replace("-", "_").upper())
create_file(h_file_path, HELPER_H_TEMPLATE, MODULE=modname, INCLUDE_GUARD=guard)
return True
@@ -393,10 +397,10 @@ def make_examples(moduledir, modname):
examplespath = Path(moduledir, "examples")
examplespath.mkdir(parents=True)
cmakelistspath = Path(examplespath, 'CMakeLists.txt')
cmakelistspath = Path(examplespath, "CMakeLists.txt")
create_file(cmakelistspath, EXAMPLES_CMAKELISTS_TEMPLATE, MODULE=modname)
examplesfile_path = examplespath.joinpath(modname+'-example').with_suffix('.cc')
examplesfile_path = examplespath.joinpath(modname + "-example").with_suffix(".cc")
create_file(examplesfile_path, EXAMPLE_CC_TEMPLATE, MODULE=modname)
return True
@@ -406,11 +410,11 @@ def make_doc(moduledir, modname):
docpath = Path(moduledir, "doc")
docpath.mkdir(parents=True)
#the module_dir template parameter must be a relative path
#instead of an absolute path
# the module_dir template parameter must be a relative path
# instead of an absolute path
mod_relpath = os.path.relpath(str(moduledir))
file_name = '{}.rst'.format(modname)
file_name = "{}.rst".format(modname)
file_path = Path(docpath, file_name)
create_file(file_path, DOC_RST_TEMPLATE, MODULE=modname, MODULE_DIR=mod_relpath)
@@ -426,8 +430,7 @@ def make_module(modpath, modname):
print("Creating module {}".format(modulepath))
functions = (make_cmakelists, make_model, make_test,
make_helper, make_examples, make_doc)
functions = (make_cmakelists, make_model, make_test, make_helper, make_examples, make_doc)
try:
modulepath.mkdir(parents=True)
@@ -447,6 +450,7 @@ def make_module(modpath, modname):
return True
def create_argument_parser():
description = """Generate scaffolding for ns-3 modules
@@ -525,25 +529,36 @@ project directory.
formatter = argparse.RawDescriptionHelpFormatter
parser = argparse.ArgumentParser(description=description,
epilog=epilog,
formatter_class=formatter)
parser = argparse.ArgumentParser(
description=description, epilog=epilog, formatter_class=formatter
)
parser.add_argument('--project', default='',
help=("Specify a relative path under the contrib directory "
"where the new modules will be generated. The path "
"will be created if it does not exist."))
parser.add_argument(
"--project",
default="",
help=(
"Specify a relative path under the contrib directory "
"where the new modules will be generated. The path "
"will be created if it does not exist."
),
)
parser.add_argument('modnames', nargs='+',
help=("One or more modules to generate. Module names "
"are limited to the following: letters, numbers, -, "
"_. Modules are generated under the contrib directory "
"except when the module name starts with src/. Modules "
"that start with src/ are generated under the src "
"directory."))
parser.add_argument(
"modnames",
nargs="+",
help=(
"One or more modules to generate. Module names "
"are limited to the following: letters, numbers, -, "
"_. Modules are generated under the contrib directory "
"except when the module name starts with src/. Modules "
"that start with src/ are generated under the src "
"directory."
),
)
return parser
def main(argv):
parser = create_argument_parser()
@@ -554,46 +569,47 @@ def main(argv):
base_path = Path.cwd()
src_path = base_path.joinpath('src')
contrib_path = base_path.joinpath('contrib')
src_path = base_path.joinpath("src")
contrib_path = base_path.joinpath("contrib")
for p in (src_path, contrib_path):
if not p.is_dir():
parser.error("Cannot find the directory '{}'.\nPlease run this "
"script from the top level of the ns3 directory".format(
p))
parser.error(
"Cannot find the directory '{}'.\nPlease run this "
"script from the top level of the ns3 directory".format(p)
)
#
# Error check the arguments
#
# Alphanumeric and '-' only
allowedRE = re.compile('^(\w|-)+$')
allowedRE = re.compile("^(\w|-)+$")
project_path = None
if project:
#project may be a path in the form a/b/c
#remove any leading or trailing path separators
# project may be a path in the form a/b/c
# remove any leading or trailing path separators
project_path = Path(project)
if project_path.is_absolute():
#remove leading separator
# remove leading separator
project_path = project_path.relative_to(os.sep)
if not all(allowedRE.match(part) for part in project_path.parts):
parser.error('Project path may only contain the characters [a-zA-Z0-9_-].')
parser.error("Project path may only contain the characters [a-zA-Z0-9_-].")
#
# Create each module, if it doesn't exist
#
modules = []
for name in modnames:
if name:
#remove any leading or trailing directory separators
# remove any leading or trailing directory separators
name = name.strip(os.sep)
if not name:
#skip empty modules
# skip empty modules
continue
name_path = Path(name)
@@ -602,33 +618,41 @@ def main(argv):
print("Skipping {}: module name can not be a path".format(name))
continue
#default target directory is contrib
# default target directory is contrib
modpath = contrib_path
if name_path.parts[0] == 'src':
if name_path.parts[0] == "src":
if project:
parser.error("{}: Cannot specify src/ in a module name when --project option is used".format(name))
parser.error(
"{}: Cannot specify src/ in a module name when --project option is used".format(
name
)
)
modpath = src_path
#create a new path without the src part
name_path = name_path.relative_to('src')
# create a new path without the src part
name_path = name_path.relative_to("src")
elif name_path.parts[0] == 'contrib':
elif name_path.parts[0] == "contrib":
modpath = contrib_path
#create a new path without the contrib part
name_path = name_path.relative_to('contrib')
# create a new path without the contrib part
name_path = name_path.relative_to("contrib")
if project_path:
#if a project path was specified, that overrides other paths
#project paths are always relative to the contrib path
# if a project path was specified, that overrides other paths
# project paths are always relative to the contrib path
modpath = contrib_path.joinpath(project_path)
modname = name_path.parts[0]
if not allowedRE.match(modname):
print("Skipping {}: module name may only contain the characters [a-zA-Z0-9_-]".format(modname))
print(
"Skipping {}: module name may only contain the characters [a-zA-Z0-9_-]".format(
modname
)
)
continue
modules.append((modpath, modname))
@@ -640,7 +664,8 @@ def main(argv):
return 0
if __name__ == '__main__':
if __name__ == "__main__":
return_value = 0
try:
return_value = main(sys.argv)

File diff suppressed because it is too large Load Diff

View File

@@ -19,6 +19,7 @@
# Author: Gustavo J. A. M. Carneiro <gjc@inescporto.pt>
import unittest
try:
from ns import ns
except ModuleNotFoundError:
@@ -60,12 +61,14 @@ class TestSimulator(unittest.TestCase):
ns.Simulator.Destroy()
self._args_received = None
self._cb_time = None
ns.cppyy.cppdef("""
ns.cppyy.cppdef(
"""
EventImpl* pythonMakeEvent(void (*f)(std::vector<std::string>), std::vector<std::string> l)
{
return MakeEvent(f, l);
}
""")
"""
)
event = ns.cppyy.gbl.pythonMakeEvent(callback, sys.argv)
ns.Simulator.ScheduleNow(event)
ns.Simulator.Run()
@@ -89,12 +92,14 @@ class TestSimulator(unittest.TestCase):
ns.Simulator.Destroy()
self._args_received = None
self._cb_time = None
ns.cppyy.cppdef("""
ns.cppyy.cppdef(
"""
EventImpl* pythonMakeEvent2(void (*f)(std::vector<std::string>), std::vector<std::string> l)
{
return MakeEvent(f, l);
}
""")
"""
)
event = ns.cppyy.gbl.pythonMakeEvent2(callback, sys.argv)
ns.Simulator.Schedule(ns.Seconds(123), event)
ns.Simulator.Run()
@@ -120,12 +125,14 @@ class TestSimulator(unittest.TestCase):
self._cb_time = None
ns.cppyy.cppdef("void null(){ return; }")
ns.Simulator.Schedule(ns.Seconds(123), ns.cppyy.gbl.null)
ns.cppyy.cppdef("""
ns.cppyy.cppdef(
"""
EventImpl* pythonMakeEvent3(void (*f)(std::vector<std::string>), std::vector<std::string> l)
{
return MakeEvent(f, l);
}
""")
"""
)
event = ns.cppyy.gbl.pythonMakeEvent3(callback, sys.argv)
ns.Simulator.ScheduleDestroy(event)
ns.Simulator.Run()
@@ -153,12 +160,14 @@ class TestSimulator(unittest.TestCase):
self._args_received = None
self._cb_time = None
self._context_received = None
ns.cppyy.cppdef("""
ns.cppyy.cppdef(
"""
EventImpl* pythonMakeEvent4(void (*f)(uint32_t, std::vector<std::string>), uint32_t context, std::vector<std::string> l)
{
return MakeEvent(f, context, l);
}
""")
"""
)
event = ns.cppyy.gbl.pythonMakeEvent4(callback, 54321, sys.argv)
ns.Simulator.ScheduleWithContext(54321, ns.Seconds(123), event)
ns.Simulator.Run()
@@ -210,20 +219,29 @@ class TestSimulator(unittest.TestCase):
def python_rx_callback(socket) -> None:
self._received_packet = socket.Recv(maxSize=UINT32_MAX, flags=0)
ns.cppyy.cppdef("""
ns.cppyy.cppdef(
"""
Callback<void,ns3::Ptr<ns3::Socket> > make_rx_callback_test_socket(void(*func)(Ptr<Socket>))
{
return MakeCallback(func);
}
""")
"""
)
sink = ns.network.Socket.CreateSocket(node, ns.core.TypeId.LookupByName("ns3::UdpSocketFactory"))
sink = ns.network.Socket.CreateSocket(
node, ns.core.TypeId.LookupByName("ns3::UdpSocketFactory")
)
sink.Bind(ns.network.InetSocketAddress(ns.network.Ipv4Address.GetAny(), 80).ConvertTo())
sink.SetRecvCallback(ns.cppyy.gbl.make_rx_callback_test_socket(python_rx_callback))
source = ns.network.Socket.CreateSocket(node, ns.core.TypeId.LookupByName("ns3::UdpSocketFactory"))
source.SendTo(ns.network.Packet(19), 0,
ns.network.InetSocketAddress(ns.network.Ipv4Address("127.0.0.1"), 80).ConvertTo())
source = ns.network.Socket.CreateSocket(
node, ns.core.TypeId.LookupByName("ns3::UdpSocketFactory")
)
source.SendTo(
ns.network.Packet(19),
0,
ns.network.InetSocketAddress(ns.network.Ipv4Address("127.0.0.1"), 80).ConvertTo(),
)
ns.Simulator.Run()
self.assertTrue(self._received_packet is not None)
@@ -297,7 +315,7 @@ class TestSimulator(unittest.TestCase):
@param self this object
@return None
"""
from ctypes import c_bool, c_int, c_double, c_char_p, create_string_buffer
from ctypes import c_bool, c_char_p, c_double, c_int, create_string_buffer
test1 = c_bool(True)
test2 = c_int(42)
@@ -362,12 +380,12 @@ class TestSimulator(unittest.TestCase):
stack.Install(nodes)
address = ns.internet.Ipv4AddressHelper()
address.SetBase(ns.network.Ipv4Address("10.1.1.0"),
ns.network.Ipv4Mask("255.255.255.0"))
address.SetBase(ns.network.Ipv4Address("10.1.1.0"), ns.network.Ipv4Mask("255.255.255.0"))
interfaces = address.Assign(devices)
ns.cppyy.cppdef("""
ns.cppyy.cppdef(
"""
namespace ns3
{
Callback<void,Ptr<Socket> > make_rx_callback(void(*func)(Ptr<Socket>))
@@ -379,7 +397,8 @@ class TestSimulator(unittest.TestCase):
return MakeEvent(f, socket, packet, address);
}
}
""")
"""
)
## EchoServer application class
class EchoServer(ns.applications.Application):
@@ -399,9 +418,14 @@ class TestSimulator(unittest.TestCase):
## Listen port for the server
self.port = port
## Socket used by the server to listen to port
self.m_socket = ns.network.Socket.CreateSocket(node,
ns.core.TypeId.LookupByName("ns3::UdpSocketFactory"))
self.m_socket.Bind(ns.network.InetSocketAddress(ns.network.Ipv4Address.GetAny(), self.port).ConvertTo())
self.m_socket = ns.network.Socket.CreateSocket(
node, ns.core.TypeId.LookupByName("ns3::UdpSocketFactory")
)
self.m_socket.Bind(
ns.network.InetSocketAddress(
ns.network.Ipv4Address.GetAny(), self.port
).ConvertTo()
)
self.m_socket.SetRecvCallback(ns.make_rx_callback(EchoServer._Receive))
EchoServer.socketToInstanceDict[self.m_socket] = self
@@ -422,13 +446,16 @@ class TestSimulator(unittest.TestCase):
self.m_socket.SendTo(packet, 0, address)
if EchoServer.LOGGING:
inetAddress = ns.InetSocketAddress.ConvertFrom(address)
print("At time +{s}s server sent {b} bytes from {ip} port {port}"
.format(s=ns.Simulator.Now().GetSeconds(),
b=packet.__deref__().GetSize(),
ip=inetAddress.GetIpv4(),
port=inetAddress.GetPort()),
file=sys.stderr,
flush=True)
print(
"At time +{s}s server sent {b} bytes from {ip} port {port}".format(
s=ns.Simulator.Now().GetSeconds(),
b=packet.__deref__().GetSize(),
ip=inetAddress.GetIpv4(),
port=inetAddress.GetPort(),
),
file=sys.stderr,
flush=True,
)
def Receive(self):
"""! Function to receive a packet from an address
@@ -439,13 +466,16 @@ class TestSimulator(unittest.TestCase):
packet = self.m_socket.RecvFrom(address)
if EchoServer.LOGGING:
inetAddress = ns.InetSocketAddress.ConvertFrom(address)
print("At time +{s}s server received {b} bytes from {ip} port {port}"
.format(s=ns.Simulator.Now().GetSeconds(),
b=packet.__deref__().GetSize(),
ip=inetAddress.GetIpv4(),
port=inetAddress.GetPort()),
file=sys.stderr,
flush=True)
print(
"At time +{s}s server received {b} bytes from {ip} port {port}".format(
s=ns.Simulator.Now().GetSeconds(),
b=packet.__deref__().GetSize(),
ip=inetAddress.GetIpv4(),
port=inetAddress.GetPort(),
),
file=sys.stderr,
flush=True,
)
event = ns.pythonMakeEventSend(EchoServer._Send, self.m_socket, packet, address)
ns.Simulator.Schedule(ns.Seconds(1), event)
@@ -493,5 +523,5 @@ class TestSimulator(unittest.TestCase):
ns.Simulator.Destroy()
if __name__ == '__main__':
if __name__ == "__main__":
unittest.main(verbosity=1, failfast=True)

View File

@@ -17,42 +17,52 @@
#
from __future__ import print_function
import sys
import subprocess
import argparse
import os
import subprocess
import sys
def print_case_in_file(case_string, out):
for i in range(100):
print("-", end='', file=out)
print("-", end="", file=out)
print(file=out)
print("running test case " + case_string, end='\n\n', file=out)
print("running test case " + case_string, end="\n\n", file=out)
out.flush()
def print_failed_cases(failed_cases):
print("\nFailed Cases:")
for case in failed_cases:
print(case)
def print_cmds(cmds):
print('Commands to be executed:')
print("Commands to be executed:")
for cmd in cmds:
print(cmd.replace(sys.executable, ''))
print(cmd.replace(sys.executable, ""))
def set_workdir():
dir_files = [f for f in os.listdir('.') if os.path.exists(f)]
if not 'VERSION' in dir_files and not 'ns3' in dir_files:
if os.path.split(os.path.abspath('.'))[1] == 'tests' and os.path.split(os.path.abspath(os.pardir))[1] == 'utils':
os.chdir('../../')
dir_files = [f for f in os.listdir(".") if os.path.exists(f)]
if not "VERSION" in dir_files and not "ns3" in dir_files:
if (
os.path.split(os.path.abspath("."))[1] == "tests"
and os.path.split(os.path.abspath(os.pardir))[1] == "utils"
):
os.chdir("../../")
else:
print('Error: Invalid working directory')
print("Error: Invalid working directory")
sys.exit(1)
## TestBaseClass class
class TestBaseClass:
"""
Generic class for testing tools based on provided commands and test cases.
Generic class for testing tools based on provided commands and test cases.
"""
## @var my_env
# os environment
## @var mode
@@ -72,9 +82,9 @@ class TestBaseClass:
"""
self.my_env = os.environ
set_workdir()
self.my_env['LD_LIBRARY_PATH'] = os.getcwd() + "/build"
self.my_env["LD_LIBRARY_PATH"] = os.getcwd() + "/build"
self.mode = mode
self.outfile = 'test-port-'+self.mode+'.out'
self.outfile = "test-port-" + self.mode + ".out"
self.options = self.parseargs(argv, desc)
def parseargs(self, argv, desc):
@@ -86,15 +96,39 @@ class TestBaseClass:
@return command line arguments
"""
parser = argparse.ArgumentParser(description=desc)
parser.add_argument('-f', '--file', action='store', dest='out_file', default=self.outfile,
metavar="FILE",
help='File to be used for storing the command specific output (Default: '+self.outfile+')')
parser.add_argument('-c', action='store_true', dest='cmds', default=False,
help='List out all the commands being tested')
parser.add_argument('-m', action='store_true', dest='mute', default=False,
help='Sends only stderr output to FILE')
parser.add_argument('-x', '--customcmd', action='store', dest='custcmd', default=None,
help='Enter a comma-separated list of commands to override the existing ones. NOT APPLICABLE FOR TEST-PY SUITE.')
parser.add_argument(
"-f",
"--file",
action="store",
dest="out_file",
default=self.outfile,
metavar="FILE",
help="File to be used for storing the command specific output (Default: "
+ self.outfile
+ ")",
)
parser.add_argument(
"-c",
action="store_true",
dest="cmds",
default=False,
help="List out all the commands being tested",
)
parser.add_argument(
"-m",
action="store_true",
dest="mute",
default=False,
help="Sends only stderr output to FILE",
)
parser.add_argument(
"-x",
"--customcmd",
action="store",
dest="custcmd",
default=None,
help="Enter a comma-separated list of commands to override the existing ones. NOT APPLICABLE FOR TEST-PY SUITE.",
)
return parser.parse_args(argv)
def override_cmds(self):
@@ -115,38 +149,39 @@ class TestBaseClass:
if self.options.cmds:
print_cmds(cmds)
return
base_dir = os.sep.join(os.path.abspath(__file__).replace(os.path.pathsep, '/').split('/')[:-3])
base_dir = os.sep.join(
os.path.abspath(__file__).replace(os.path.pathsep, "/").split("/")[:-3]
)
final_return = 0
total_tests = len(cmds)
passed = 0
progress = 0.0
failed_cases = []
with open(self.options.out_file, 'w', encoding='utf-8') as out:
with open(self.options.out_file, "w", encoding="utf-8") as out:
outstream = out
with open(os.devnull, 'w', encoding='utf-8') as sink:
with open(os.devnull, "w", encoding="utf-8") as sink:
if self.options.mute:
outstream = sink
for cmd in cmds:
case_string = cmd.replace(sys.executable, '')
case_string = cmd.replace(sys.executable, "")
print("running test case: " + case_string)
print_case_in_file(case_string, out)
progress += 1
ret = subprocess.call(cmd,
shell=True,
env=self.my_env,
stdout=outstream,
stderr=out,
cwd=base_dir
)
ret = subprocess.call(
cmd, shell=True, env=self.my_env, stdout=outstream, stderr=out, cwd=base_dir
)
if not ret:
passed += 1
else:
final_return = 1
failed_cases.append(case_string)
print("[ %s out of %s ] test cases passed; Progress = %.2f%% \n" % (passed, total_tests, progress*100/total_tests))
print(
"[ %s out of %s ] test cases passed; Progress = %.2f%% \n"
% (passed, total_tests, progress * 100 / total_tests)
)
if final_return != 0:
print_failed_cases(failed_cases)
else:
print("\nAll cases passed")
print("Detailed output available in " + self.options.out_file, end='\n\n')
print("Detailed output available in " + self.options.out_file, end="\n\n")
return final_return

File diff suppressed because it is too large Load Diff

View File

@@ -61,61 +61,67 @@
# write detailed test results into XML-FILE.xml
from __future__ import print_function
from TestBase import TestBaseClass
import sys
from TestBase import TestBaseClass
def main(argv):
"""
Prepares test cases and executes
Prepares test cases and executes
"""
test_cases = [
'',
'-h',
'--help',
'-b build/',
'--buildpath=build/',
'-c performance',
'--constrain=performance',
'-d',
'--duration',
'-e socket-options-ipv6',
'--example=socket-options-ipv6',
'-u',
'--update-data',
'-f EXTENSIVE',
'--fullness=EXTENSIVE',
'-g',
'--grind',
'-l',
'--list',
'-m',
'--multiple',
'-n',
'--no-build',
'-p first',
'--pyexample=first',
'-r',
'--retain',
'-s ns3-tcp-state',
'--suite=ns3-tcp-state',
'-t t_opt.txt',
'--text=t_opt.txt && rm t_opt.txt',
'-v',
'--verbose',
'-w t_opt.html && rm t_opt.html',
'--web=t_opt.html && rm t_opt.html',
'--html=t_opt.html && rm t_opt.html',
'-x t_opt.xml && rm t_opt.xml',
'--xml=t_opt.xml && rm t_opt.xml',
"",
"-h",
"--help",
"-b build/",
"--buildpath=build/",
"-c performance",
"--constrain=performance",
"-d",
"--duration",
"-e socket-options-ipv6",
"--example=socket-options-ipv6",
"-u",
"--update-data",
"-f EXTENSIVE",
"--fullness=EXTENSIVE",
"-g",
"--grind",
"-l",
"--list",
"-m",
"--multiple",
"-n",
"--no-build",
"-p first",
"--pyexample=first",
"-r",
"--retain",
"-s ns3-tcp-state",
"--suite=ns3-tcp-state",
"-t t_opt.txt",
"--text=t_opt.txt && rm t_opt.txt",
"-v",
"--verbose",
"-w t_opt.html && rm t_opt.html",
"--web=t_opt.html && rm t_opt.html",
"--html=t_opt.html && rm t_opt.html",
"-x t_opt.xml && rm t_opt.xml",
"--xml=t_opt.xml && rm t_opt.xml",
]
configure_string = sys.executable + ' ns3 configure --enable-tests --enable-examples'
clean_string = sys.executable + ' ns3 clean'
cmd_execute_list = ['%s && %s test.py %s && %s' % (configure_string, sys.executable, option, clean_string) for option in test_cases]
runner = TestBaseClass(argv[1:], "Test suite for the ns-3 unit test runner", 'test-py')
configure_string = sys.executable + " ns3 configure --enable-tests --enable-examples"
clean_string = sys.executable + " ns3 clean"
cmd_execute_list = [
"%s && %s test.py %s && %s" % (configure_string, sys.executable, option, clean_string)
for option in test_cases
]
runner = TestBaseClass(argv[1:], "Test suite for the ns-3 unit test runner", "test-py")
return runner.runtests(cmd_execute_list)
if __name__ == '__main__':
if __name__ == "__main__":
sys.exit(main(sys.argv))