From 562ddfdc74d5cf730d5db2c8d689b781f2613310 Mon Sep 17 00:00:00 2001 From: Jinguang Dong Date: Tue, 17 Sep 2024 17:49:48 +0800 Subject: [PATCH] Description:[feature] opensource tools for generate and validate Bug: https://gitee.com/openharmony/developtools_integration_verification/issues/IARN6Y Test: run generate_readme_opensource and validate_readme_opensource tools Signed-off-by: Jinguang Dong --- tools/opensource_tools/README_OSS.md | 317 ++++++++++++++++++ .../src/generate_readme_opensource.py | 48 +++ .../src/validate_readme_opensource.py | 63 ++++ .../test/test_generate_readme_opensource.py | 70 ++++ .../test/test_validate_readme_opensource.py | 65 ++++ 5 files changed, 563 insertions(+) create mode 100644 tools/opensource_tools/README_OSS.md create mode 100644 tools/opensource_tools/src/generate_readme_opensource.py create mode 100644 tools/opensource_tools/src/validate_readme_opensource.py create mode 100644 tools/opensource_tools/test/test_generate_readme_opensource.py create mode 100644 tools/opensource_tools/test/test_validate_readme_opensource.py diff --git a/tools/opensource_tools/README_OSS.md b/tools/opensource_tools/README_OSS.md new file mode 100644 index 0000000..081e713 --- /dev/null +++ b/tools/opensource_tools/README_OSS.md @@ -0,0 +1,317 @@ +# 开源软件README.OpenSource工具使用文档 + +## 目录 + +- [开源软件README.OpenSource工具使用文档](#开源软件readmeopensource工具使用文档) + - [目录](#目录) + - [简介](#简介) + - [功能概述](#功能概述) + - [工作流程概览](#工作流程概览) + - [生成工具工作流程](#生成工具工作流程) + - [验证工具工作流程](#验证工具工作流程) + - [安装与环境配置](#安装与环境配置) + - [环境要求](#环境要求) + - [安装步骤](#安装步骤) + - [使用指南](#使用指南) + - [生成 `README.OpenSource` 文件](#生成-readmeopensource-文件) + - [步骤](#步骤) + - [示例](#示例) + - [生成工具流程图](#生成工具流程图) + - [验证 `README.OpenSource` 文件](#验证-readmeopensource-文件) + - [步骤](#步骤-1) + - [示例](#示例-1) + - [验证工具流程图](#验证工具流程图) + - [处理验证错误](#处理验证错误) + - [测试与验证](#测试与验证) + - [自动化测试](#自动化测试) + - [运行测试](#运行测试) + - [预期结果](#预期结果) + - [手动测试](#手动测试) + - [生成工具测试](#生成工具测试) + - [验证工具测试](#验证工具测试) + - [目录结构](#目录结构) + +## 简介 + +本开源软件工具旨在为项目中的每个开源组件创建标准化的 `README.OpenSource` 文件,并提供验证这些文件格式和内容的功能。该工具仅使用 Python 标准库开发,易于安装和使用。 + +## 功能概述 + +- **生成工具**:`generate_readme_opensource.py` + - 通过交互方式,用户输入开源组件的信息,支持多个组件的输入。 + - 生成符合规范的 `README.OpenSource` 文件,包含所有输入的组件信息。 +- **验证工具**:`validate_readme_opensource.py` + - 验证项目中所有 `README.OpenSource` 文件的格式和内容。 + - 检查必需字段是否完整,JSON 格式是否正确。 + +### 工作流程概览 + +下面是对应的工具的工作流程示意图 + +#### 生成工具工作流程 + +```mermaid +flowchart TD + A[开始] --> B[输入输出目录和开源部件信息] + B --> C{是否继续添加开源部件?} + C -->|是| D[输入开源部件信息] + D --> C + C -->|否| E[生成README.OpenSource文件] + E --> F[结束] + +``` + +#### 验证工具工作流程 + +```mermaid +flowchart TD + A[开始] --> B[输入要验证的目录] + B --> C[搜索README.OpenSource文件] + C --> D{找到文件?} + D -->|是| E[读取并验证文件] + E --> F{验证通过?} + F -->|是| G[输出验证成功信息] + F -->|否| H[输出错误信息] + D -->|否| I[输出未找到文件信息] + G & H & I --> J[结束] + +``` + + + +## 安装与环境配置 + +### 环境要求 + +- **Python 版本**:Python 3.6 或更高版本 +- **操作系统**:跨平台,支持 Linux/Windows + +### 安装步骤 + +1. **克隆或下载项目代码** + + ``` + git clone https://gitee.com/openharmony/developtools_integration_verification.git + cd tools/opensource_tools + ``` + +2. **(可选)创建虚拟环境** + + ``` + python -m venv venv + source venv/bin/activate # Linux/macOS + # 或 + venv\Scripts\activate # Windows + ``` + +3. **安装依赖** + + 由于该工具仅使用 Python 标准库,无需安装额外依赖。 + +## 使用指南 + +### 生成 `README.OpenSource` 文件 + +运行 `generate_readme_opensource.py` 脚本,按照提示输入开源组件的信息。 + +#### 步骤 + +1. **进入项目目录** + + ``` + cd src + ``` + +2. **运行生成脚本** + + ``` + python generate_readme_opensource.py + ``` + +3. **按照提示输入信息** + + - 脚本将提示您输入输出目录,默认为当前目录。 + - 输入每个组件的详细信息,包括: + - Name + - License + - License File + - Version Number + - Owner + - Upstream URL + - Description + - 输入完成后,选择是否添加另一个组件。 + +4. **完成生成** + + - 脚本将在指定的输出目录下生成 `README.OpenSource` 文件。 + +#### 示例 + +``` +请输入输出目录(默认当前目录): +Name: elfutils +License: LGPL-2.1, LGPL-3.0, GPL-2.0 +License File: COPYING-GPLV2 +Version Number: 0.188 +Owner: opensource@sourceware.org +Upstream URL: https://sourceware.org/elfutils/ +Description: A collection of tools and libraries. +是否添加另一个组件?(y/n): y +Name: OpenSSL +License: Apache-2.0 +License File: LICENSE +Version Number: 1.1.1 +Owner: opensource@openssl.org +Upstream URL: https://www.openssl.org/ +Description: A toolkit for TLS and SSL protocols. +是否添加另一个组件?(y/n): n +已生成 ./README.OpenSource +``` + +#### 生成工具流程图 + +```mermaid +sequenceDiagram + participant User as 用户 + participant Script as generate_readme_opensource.py + User->>Script: 运行脚本 + Script-->>User: 请输入输出目录 + User->>Script: 输入输出目录 + Script-->>User: 请输入部件信息 + User->>Script: 输入开源部件信息 + loop 添加开源部件 + Script-->>User: 请输入部件信息 + User->>Script: 输入开源部件信息 + Script-->>User: 是否添加另一个开源部件?(y/n) + User->>Script: y 或 n + end + Script->>FileSystem: 生成README.OpenSource文件 + Script-->>User: 已生成 README.OpenSource +``` + + + +### 验证 `README.OpenSource` 文件 + +运行 `validate_readme_opensource.py` 脚本,验证指定目录中的 `README.OpenSource` 文件。 + +#### 步骤 + +1. **进入项目目录** + + ``` + cd src + ``` + +2. **运行验证脚本** + + ``` + python validate_readme_opensource.py [目录路径] + ``` + + - 如果不指定目录路径,默认验证当前目录。 + +3. **查看验证结果** + + - 脚本将输出验证结果,指示文件是否有效。 + - 如果有错误,脚本会列出具体的错误信息。 + +#### 示例 + +``` +python validate_readme_opensource.py . + +./README.OpenSource is valid. +``` + +#### 验证工具流程图 + +```mermaid +sequenceDiagram + participant User as 用户 + participant Script as validate_readme_opensource.py + User->>Script: 运行脚本 + Script->>FileSystem: 搜索 README.OpenSource 文件 + alt 找到文件 + Script->>Script: 验证文件内容 + alt 验证通过 + Script-->>User: 文件有效 + else 验证失败 + Script-->>User: 文件无效,输出错误信息 + end + else 未找到文件 + Script-->>User: 未找到 README.OpenSource 文件 + end +``` + + + +#### 处理验证错误 + +如果验证失败,脚本会输出类似以下的错误信息: + +``` +./README.OpenSource is invalid: + - Component 1 is missing required field: License + - JSON decode error: Expecting value: line 10 column 5 (char 250) +``` + +请根据提示修复 `README.OpenSource` 文件中的错误,然后重新运行验证脚本。 + +## 测试与验证 + +### 自动化测试 + +项目包含自动化测试用例,确保工具的可靠性。 + +#### 运行测试 + +1. **进入项目根目录** + + ``` + cd opensource_tools + ``` + +2. **运行所有测试用例** + + ``` + python -m unittest discover -s test + ``` + +#### 预期结果 + +``` +.. +---------------------------------------------------------------------- +Ran 3 tests in 0.001s + +OK +``` + +### 手动测试 + +#### 生成工具测试 + +- **测试正常输入**:按照使用指南运行生成脚本,输入多个组件的信息,检查生成的 `README.OpenSource` 文件内容是否正确。 +- **测试边界情况**:输入空值、特殊字符、超长字符串,观察脚本是否能正常处理。 + +#### 验证工具测试 + +- **验证正确的文件**:使用生成的正确的 `README.OpenSource` 文件,运行验证脚本,确保验证通过。 +- **验证错误的文件**:手动修改 `README.OpenSource` 文件,引入格式错误或缺少字段,运行验证脚本,检查是否能正确捕获错误。 + +## 目录结构 + +以下是项目的目录结构: + +- src/ + + - `generate_readme_opensource.py`:生成工具脚本 + - `validate_readme_opensource.py`:验证工具脚本 + +- test/ + - `test_generate_readme_opensource.py`:生成工具测试用例 + - `test_validate_readme_opensource.py`:验证工具测试用例 + +- **README_OSS.md**:使用文档 + diff --git a/tools/opensource_tools/src/generate_readme_opensource.py b/tools/opensource_tools/src/generate_readme_opensource.py new file mode 100644 index 0000000..8d4cdfc --- /dev/null +++ b/tools/opensource_tools/src/generate_readme_opensource.py @@ -0,0 +1,48 @@ +import os +import json + +def ask_question(prompt): + return input(prompt).strip() + +def generate_readme_opensource(output_dir): + """ + 生成 README.OpenSource 文件,支持多个开源组件的信息输入。 + """ + components = [] + fields = [ + "Name", + "License", + "License File", + "Version Number", + "Owner", + "Upstream URL", + "Description" + ] + + print("请输入开源组件的信息(输入完成后,可选择继续添加另一个组件):") + while True: + component = {} + for field in fields: + value = ask_question(f"{field}: ") + component[field] = value + components.append(component) + + add_more = ask_question("是否添加另一个组件?(y/n): ").lower() + if add_more != 'y': + break + + if not os.path.exists(output_dir): + os.makedirs(output_dir) + + readme_path = os.path.join(output_dir, 'README.OpenSource') + with open(readme_path, 'w', encoding='utf-8') as f: + json.dump(components, f, indent=2, ensure_ascii=False) + print(f"已生成 {readme_path}") + +def main(): + output_dir = ask_question("请输入输出目录(默认当前目录):") or '.' + generate_readme_opensource(output_dir) + +if __name__ == '__main__': + main() + diff --git a/tools/opensource_tools/src/validate_readme_opensource.py b/tools/opensource_tools/src/validate_readme_opensource.py new file mode 100644 index 0000000..e68d0f9 --- /dev/null +++ b/tools/opensource_tools/src/validate_readme_opensource.py @@ -0,0 +1,63 @@ +# src/validate_readme_opensource.py + +import os +import json +import argparse + +REQUIRED_FIELDS = [ + "Name", + "License", + "License File", + "Version Number", + "Owner", + "Upstream URL", + "Description" +] + +def validate_readme_opensource(file_path): + """ + 验证 README.OpenSource 文件的格式和内容。 + + :param file_path: README.OpenSource 文件路径 + :return: 是否验证通过,错误信息列表 + """ + errors = [] + try: + with open(file_path, 'r', encoding='utf-8') as f: + data = json.load(f) + if not isinstance(data, list): + errors.append("The file does not contain a JSON array.") + return False, errors + + for idx, component in enumerate(data): + for field in REQUIRED_FIELDS: + if field not in component: + errors.append(f"Component {idx + 1} is missing required field: {field}") + except json.JSONDecodeError as e: + errors.append(f"JSON decode error: {e}") + return False, errors + except Exception as e: + errors.append(f"Unexpected error: {e}") + return False, errors + + return len(errors) == 0, errors + +def main(): + parser = argparse.ArgumentParser(description='Validate README.OpenSource files.') + parser.add_argument('directory', help='Directory to search for README.OpenSource files.') + args = parser.parse_args() + + for root, dirs, files in os.walk(args.directory): + if 'README.OpenSource' in files: + file_path = os.path.join(root, 'README.OpenSource') + valid, errors = validate_readme_opensource(file_path) + if valid: + print(f"{file_path} is valid.") + else: + print(f"{file_path} is invalid:") + for error in errors: + print(f" - {error}") + +if __name__ == '__main__': + main() + diff --git a/tools/opensource_tools/test/test_generate_readme_opensource.py b/tools/opensource_tools/test/test_generate_readme_opensource.py new file mode 100644 index 0000000..4b5f76b --- /dev/null +++ b/tools/opensource_tools/test/test_generate_readme_opensource.py @@ -0,0 +1,70 @@ +import unittest +import os +import json +import shutil +from unittest.mock import patch +from src.generate_readme_opensource import generate_readme_opensource + +class TestGenerateReadmeOpenSource(unittest.TestCase): + + def setUp(self): + self.test_output_dir = 'test_output' + if not os.path.exists(self.test_output_dir): + os.makedirs(self.test_output_dir) + + def tearDown(self): + if os.path.exists(self.test_output_dir): + shutil.rmtree(self.test_output_dir) + + @patch('builtins.input', side_effect=[ + # First component + 'elfutils', # Name + 'LGPL-2.1, LGPL-3.0, GPL-2.0', # License + 'COPYING-GPLV2', # License File + '0.188', # Version Number + 'zhanghaibo0@huawei.com', # Owner + 'https://sourceware.org/elfutils/', # Upstream URL + 'A collection of tools and libraries.', # Description + 'y', # Add another component + # Second component + 'OpenSSL', # Name + 'Apache-2.0', # License + 'LICENSE', # License File + '1.1.1', # Version Number + 'opensource@openssl.org', # Owner + 'https://www.openssl.org/', # Upstream URL + 'A robust, commercial-grade, full-featured toolkit for the TLS and SSL protocols.', # Description + 'n' # Do not add another component + ]) + def test_generate_readme_opensource(self, mock_inputs): + generate_readme_opensource(self.test_output_dir) + readme_path = os.path.join(self.test_output_dir, 'README.OpenSource') + self.assertTrue(os.path.exists(readme_path)) + + with open(readme_path, 'r', encoding='utf-8') as f: + data = json.load(f) + expected_data = [ + { + "Name": "elfutils", + "License": "LGPL-2.1, LGPL-3.0, GPL-2.0", + "License File": "COPYING-GPLV2", + "Version Number": "0.188", + "Owner": "zhanghaibo0@huawei.com", + "Upstream URL": "https://sourceware.org/elfutils/", + "Description": "A collection of tools and libraries." + }, + { + "Name": "OpenSSL", + "License": "Apache-2.0", + "License File": "LICENSE", + "Version Number": "1.1.1", + "Owner": "opensource@openssl.org", + "Upstream URL": "https://www.openssl.org/", + "Description": "A robust, commercial-grade, full-featured toolkit for the TLS and SSL protocols." + } + ] + self.assertEqual(data, expected_data) + +if __name__ == '__main__': + unittest.main() + diff --git a/tools/opensource_tools/test/test_validate_readme_opensource.py b/tools/opensource_tools/test/test_validate_readme_opensource.py new file mode 100644 index 0000000..d77f7b7 --- /dev/null +++ b/tools/opensource_tools/test/test_validate_readme_opensource.py @@ -0,0 +1,65 @@ +# test/test_validate_readme_opensource.py + +import sys +import os +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +import unittest +import os +import json +import shutil +from src.validate_readme_opensource import validate_readme_opensource + +class TestValidateReadmeOpenSource(unittest.TestCase): + + def setUp(self): + self.test_dir = 'test_validate' + if not os.path.exists(self.test_dir): + os.makedirs(self.test_dir) + self.valid_readme = [ + { + "Name": "elfutils", + "License": "LGPL-2.1, LGPL-3.0, GPL-2.0", + "License File": "COPYING-GPLV2", + "Version Number": "0.188", + "Owner": "zhanghaibo0@huawei.com", + "Upstream URL": "https://sourceware.org/elfutils/", + "Description": "A collection of tools and libraries." + } + ] + self.invalid_readme = [ + { + "Name": "elfutils", + "License": "LGPL-2.1, LGPL-3.0, GPL-2.0", + # Missing 'License File' + "Version Number": "0.188", + "Owner": "zhanghaibo0@huawei.com", + "Upstream URL": "https://sourceware.org/elfutils/", + "Description": "A collection of tools and libraries." + } + ] + self.valid_path = os.path.join(self.test_dir, 'valid_README.OpenSource') + self.invalid_path = os.path.join(self.test_dir, 'invalid_README.OpenSource') + + with open(self.valid_path, 'w', encoding='utf-8') as f: + json.dump(self.valid_readme, f, indent=2, ensure_ascii=False) + + with open(self.invalid_path, 'w', encoding='utf-8') as f: + json.dump(self.invalid_readme, f, indent=2, ensure_ascii=False) + + def tearDown(self): + if os.path.exists(self.test_dir): + shutil.rmtree(self.test_dir) + + def test_validate_valid_readme(self): + valid, errors = validate_readme_opensource(self.valid_path) + self.assertTrue(valid) + self.assertEqual(len(errors), 0) + + def test_validate_invalid_readme(self): + valid, errors = validate_readme_opensource(self.invalid_path) + self.assertFalse(valid) + self.assertIn("Component 1 is missing required field: License File", errors) + +if __name__ == '__main__': + unittest.main()