Selaa lähdekoodia

ci: 添加单元测试和集成测试 (#36)

* ci: 添加单元测试和集成测试
LoGin 3 viikkoa sitten
vanhempi
commit
60f366c9cf

+ 62 - 0
.github/issue-checker.yml

@@ -0,0 +1,62 @@
+default-mode:
+  add:
+  remove: [pull_request_target, issues]
+labels:
+# skips and removes
+- name: skip all
+  content:
+  regexes: '[Ss]kip (?:[Aa]ll |)[Ll]abels?'
+- name: remove all
+  content:
+  regexes: '[Rr]emove (?:[Aa]ll |)[Ll]abels?'
+- name: skip ambiguous
+  content:
+  regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)ambiguous(?:`|)'
+- name: remove ambiguous
+  content:
+  regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)ambiguous(?:`|)'
+# `feature`
+- name: enhance
+  content: enhancement
+  regexes: '[Ff]eat(?:\([a-zA-Z]*\))?[\:\.\,]'
+  skip-if:
+  - skip all
+  remove-if:
+  - remove all
+# `Bug fix`
+- name: bug-fix
+  content: Bug fix
+  regexes: '[Ff]ix(?:\([a-zA-Z]*\))?[\:\.\,]'
+  skip-if:
+  - skip all
+  remove-if:
+  - remove all
+# `document`
+- name: doc
+  content: documentation
+  regexes: '[Dd]ocs(?:\([a-zA-Z]*\))?[\:\.\,]'
+  skip-if:
+  - skip all
+  remove-if:
+  - remove all
+# `test`
+- name: test
+  content: test
+  regexes: '[Tt]est(?:\([a-zA-Z]*\))?[\:\.\,]'
+  skip-if:
+  - skip all
+  remove-if:
+  - remove all
+# `ambiguous`
+- name: pr-ambiguous
+  # 不符合 commitizen 的 PR
+  content: ambiguous
+  regexes: '^(?!(?:build|chore|ci|docs?|feat|fix|perf|refactor|rft|style|test)(?:\([a-zA-Z]*\))?[\:\.\(\,]|[Rr]evert|[Rr]elease)'
+  mode:
+    pull_request_target:
+  skip-if:
+  - skip all
+  - skip ambiguous
+  remove-if:
+  - remove all
+  - remove ambiguous

+ 8 - 0
.github/workflows/rust.yml

@@ -28,3 +28,11 @@ jobs:
 
     - name: Check formatting
       run: cargo fmt --check
+
+  test:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v3
+
+    - name: Run tests
+      run: cargo test

+ 9 - 0
Cargo.toml

@@ -8,6 +8,11 @@ license = "GPL-2.0-only"
 repository = "https://github.com/DragonOS-Community/DADK.git"
 readme = "README.md"
 
+[workspace]
+members = [
+    "crates/test_base",
+]
+
 [[bin]]
 name = "dadk"
 path = "src/main.rs"
@@ -27,3 +32,7 @@ serde_json = "1.0.96"
 simple_logger = { version = "4.1.0", features = ["stderr"] }
 toml = "0.8.12"
 zip = "0.6"
+
+[dev-dependencies]
+test_base = { path = "crates/test_base" }
+

+ 11 - 0
crates/test_base/Cargo.toml

@@ -0,0 +1,11 @@
+[package]
+name = "test_base"
+version = "0.1.0"
+edition = "2021"
+license = "GPL-2.0-only"
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+log = "0.4.21"
+simple_logger = "4.3.3"
+test-context = "0.3.0"

+ 41 - 0
crates/test_base/src/lib.rs

@@ -0,0 +1,41 @@
+pub extern crate test_context;
+
+use std::path::PathBuf;
+
+use simple_logger::SimpleLogger;
+use test_context::TestContext;
+
+pub struct BaseTestContext {
+    /// 项目的根目录
+    project_base_path: PathBuf,
+}
+
+impl BaseTestContext {
+    /// 获取项目的根目录
+    pub fn project_base_path(&self) -> &PathBuf {
+        &self.project_base_path
+    }
+
+    /// 获取项目目录下的文件的的绝对路径
+    pub fn abs_path(&self, relative_path: &str) -> PathBuf {
+        self.project_base_path.join(relative_path)
+    }
+
+    /// 获取`xxx.dadk`配置文件的目录
+    pub fn config_v1_dir(&self) -> PathBuf {
+        self.abs_path("tests/data/dadk_config_v1")
+    }
+}
+
+impl TestContext for BaseTestContext {
+    fn setup() -> Self {
+        let logger = SimpleLogger::new().with_level(log::LevelFilter::Debug);
+
+        logger.init().unwrap();
+        // 获取DADK项目的根目录
+        let mut project_base_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+        project_base_path.pop();
+        project_base_path.pop();
+        BaseTestContext { project_base_path }
+    }
+}

+ 1 - 1
rust-toolchain.toml

@@ -1,3 +1,3 @@
 [toolchain]
-channel = "nightly-2023-08-15"
+channel = "nightly-2024-04-25"
 components = ["rust-src", "rustfmt"]

+ 1 - 0
src/console/interactive.rs

@@ -72,6 +72,7 @@ pub trait InputFunc<T: Debug + Sized> {
     /// # 读取用户输入,最多重试指定次数
     ///
     /// 如果重试次数超过指定次数,则返回错误Err(ConsoleError::RetryLimitExceeded)
+    #[allow(dead_code)]
     fn input_with_retry(&mut self, retry: usize) -> Result<T, ConsoleError> {
         for _ in 0..retry {
             let task_type = self.input();

+ 1 - 0
src/console/mod.rs

@@ -73,6 +73,7 @@ pub enum Action {
     New,
 }
 
+#[allow(dead_code)]
 #[derive(Debug)]
 pub enum ConsoleError {
     CommandError(String),

+ 3 - 3
src/executor/source.rs

@@ -17,7 +17,7 @@ use super::cache::CacheDir;
 /// # Git源
 ///
 /// 从Git仓库获取源码
-#[derive(Debug, Clone, Serialize, Deserialize)]
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
 pub struct GitSource {
     /// Git仓库地址
     url: String,
@@ -448,7 +448,7 @@ impl GitSource {
 }
 
 /// # 本地源
-#[derive(Debug, Clone, Serialize, Deserialize)]
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
 pub struct LocalSource {
     /// 本地目录/文件的路径
     path: PathBuf,
@@ -486,7 +486,7 @@ impl LocalSource {
 }
 
 /// # 在线压缩包源
-#[derive(Debug, Clone, Serialize, Deserialize)]
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
 pub struct ArchiveSource {
     /// 压缩包的URL
     url: String,

+ 208 - 0
src/lib.rs

@@ -0,0 +1,208 @@
+//! # DADK - DragonOS Application Development Kit
+//! # DragonOS 应用开发工具
+//!
+//! ## 简介
+//!
+//! DADK是一个用于开发DragonOS应用的工具包,设计目的是为了让开发者能够更加方便的开发DragonOS应用。
+//!
+//! ### DADK做什么?
+//!
+//! - 自动配置libc等编译用户程序所需的环境
+//! - 自动处理软件库的依赖关系
+//! - 自动处理软件库的编译
+//! - 一键将软件库安装到DragonOS系统中
+//!
+//! ### DADK不做什么?
+//!
+//! - DADK不会帮助开发者编写代码
+//! - DADK不提供任何开发DragonOS应用所需的API。这部分工作由libc等库来完成
+//!
+//! ## License
+//!
+//! DADK is licensed under the [GPLv2 License](https://www.gnu.org/licenses/old-licenses/gpl-2.0.html).
+//!
+//! ## 快速开始
+//!
+//! ### 安装DADK
+//!
+//! DADK是一个Rust程序,您可以通过Cargo来安装DADK。
+//!
+//! ```shell
+//! # 从GitHub安装最新版
+//! cargo install --git https://github.com/DragonOS-Community/DADK.git
+//!
+//! # 从crates.io下载
+//! cargo install dadk
+//!
+//! ```
+//!
+//! ## DADK的工作原理
+//!
+//! DADK使用(任务名,任务版本)来标识每个构建目标。当使用DADK构建DragonOS应用时,DADK会根据用户的配置文件,自动完成以下工作:
+//!
+//! - 解析配置文件,生成DADK任务列表
+//! - 根据DADK任务列表,进行拓扑排序。这一步会自动处理软件库的依赖关系。
+//! - 收集环境变量信息,并根据DADK任务列表,设置全局环境变量、任务环境变量。
+//! - 根据拓扑排序后的DADK任务列表,自动执行任务。
+//!
+//! ### DADK与环境变量
+//!
+//! 环境变量的设置是DADK能正常工作的关键因素之一,您可以在您的编译脚本中,通过引用环境变量,来获得其他软件库的编译信息。
+//! 这是使得您的应用能够自动依赖其他软件库的关键一步。
+//!
+//! 只要您的编译脚本能够正确地引用环境变量,DADK就能够自动处理软件库的依赖关系。
+//!
+//! DADK会设置以下全局环境变量:
+//!
+//! - `DADK_CACHE_ROOT`:DADK的缓存根目录。您可以在编译脚本中,通过引用该环境变量,来获得DADK的缓存根目录。
+//! - `DADK_BUILD_CACHE_DIR_任务名_任务版本`:DADK的任务构建结果缓存目录。当您要引用其他软件库的构建结果时,可以通过该环境变量来获得。
+//! 同时,您也要在构建您的app时,把构建结果放到您的软件库的构建结果缓存目录(通过对应的环境变量获得)中。
+//! - `DADK_SOURCE_CACHE_DIR_任务名_任务版本`:DADK的某个任务的源码目录。当您要引用其他软件库的源码目录时,可以通过该环境变量来获得。
+//!
+//! 同时,DADK会为每个任务设置其自身在配置文件中指定的环境变量。
+//!
+//! #### 全局环境变量命名格式
+//!
+//! 全局环境变量中的任务名和任务版本,都会被转换为大写字母,并对特殊字符进行替换。替换表如下:
+//!
+//! | 原字符 | 替换字符 |
+//! | ------ | -------- |
+//! | `.`    | `_`      |
+//! | `-`    | `_`      |
+//! | `\t`   | `_`      |
+//! | 空格   | `_`      |
+//! | `+`    | `_`      |
+//! | `*`    | `_`      |
+//!
+//! **举例**:对于任务`libc-0.1.0`,其构建结果的全局环境变量名为`DADK_BUILD_CACHE_DIR_LIBC_0_1_0`。
+//!
+//!
+//! ## TODO
+//!
+//! - 支持从在线归档文件下载源码、构建好的软件库
+//! - 支持自动更新
+//! - 完善clean命令的逻辑
+
+#![feature(extract_if)]
+#![feature(io_error_more)]
+
+#[macro_use]
+extern crate lazy_static;
+extern crate log;
+extern crate serde;
+extern crate serde_json;
+extern crate simple_logger;
+
+use std::{path::PathBuf, process::exit};
+
+use clap::Parser;
+
+use log::{error, info};
+use parser::task::DADKTask;
+use simple_logger::SimpleLogger;
+
+use crate::{
+    console::{interactive::InteractiveConsole, CommandLineArgs},
+    executor::cache::cache_root_init,
+    scheduler::{task_deque::TASK_DEQUE, Scheduler},
+};
+
+mod console;
+mod executor;
+pub mod parser;
+mod scheduler;
+pub mod static_resources;
+mod utils;
+
+pub fn dadk_main() {
+    logger_init();
+    // generate_tmp_dadk();
+    info!("DADK Starting...");
+    let args = CommandLineArgs::parse();
+
+    info!("DADK run with args: {:?}", &args);
+    // DragonOS sysroot在主机上的路径
+    let dragonos_dir = args.dragonos_dir.clone();
+    let config_dir = args.config_dir.clone();
+    let action = args.action;
+    let thread = args.thread;
+    info!(
+        "DragonOS sysroot dir: {}",
+        dragonos_dir
+            .as_ref()
+            .map_or_else(|| "None".to_string(), |d| d.display().to_string())
+    );
+    info!(
+        "Config dir: {}",
+        config_dir
+            .as_ref()
+            .map_or_else(|| "None".to_string(), |d| d.display().to_string())
+    );
+    info!("Action: {:?}", action);
+    info!(
+        "Thread num: {}",
+        thread
+            .as_ref()
+            .map_or_else(|| "None".to_string(), |d| d.to_string())
+    );
+
+    match action {
+        console::Action::New => {
+            let r = InteractiveConsole::new(dragonos_dir.clone(), config_dir.clone(), action).run();
+            if r.is_err() {
+                error!("Failed to run interactive console: {:?}", r.unwrap_err());
+                exit(1);
+            }
+            exit(0);
+        }
+        _ => {}
+    }
+
+    if let Some(thread) = thread {
+        TASK_DEQUE.lock().unwrap().set_thread(thread);
+    }
+
+    // 初始化缓存目录
+    let r = cache_root_init(args.cache_dir);
+    if r.is_err() {
+        error!("Failed to init cache root: {:?}", r.unwrap_err());
+        exit(1);
+    }
+
+    let config_dir = args.config_dir.unwrap_or_else(|| {
+        error!("Config dir not specified");
+        exit(1);
+    });
+
+    let dragonos_dir = args.dragonos_dir.unwrap_or_else(|| {
+        error!("DragonOS sysroot dir not specified");
+        exit(1);
+    });
+
+    let mut parser = parser::Parser::new(config_dir);
+    let r = parser.parse();
+    if r.is_err() {
+        exit(1);
+    }
+    let tasks: Vec<(PathBuf, DADKTask)> = r.unwrap();
+    // info!("Parsed tasks: {:?}", tasks);
+
+    let scheduler = Scheduler::new(dragonos_dir, action, tasks);
+    if scheduler.is_err() {
+        exit(1);
+    }
+
+    let r = scheduler.unwrap().run();
+    if r.is_err() {
+        exit(1);
+    }
+}
+
+/// 初始化日志系统
+fn logger_init() {
+    // 初始化日志系统,日志级别为Info
+    // 如果需要调试,可以将日志级别设置为Debug
+    let logger = SimpleLogger::new().with_level(log::LevelFilter::Info);
+
+    logger.init().unwrap();
+}

+ 2 - 205
src/main.rs

@@ -1,208 +1,5 @@
-//! # DADK - DragonOS Application Development Kit
-//! # DragonOS 应用开发工具
-//!
-//! ## 简介
-//!
-//! DADK是一个用于开发DragonOS应用的工具包,设计目的是为了让开发者能够更加方便的开发DragonOS应用。
-//!
-//! ### DADK做什么?
-//!
-//! - 自动配置libc等编译用户程序所需的环境
-//! - 自动处理软件库的依赖关系
-//! - 自动处理软件库的编译
-//! - 一键将软件库安装到DragonOS系统中
-//!
-//! ### DADK不做什么?
-//!
-//! - DADK不会帮助开发者编写代码
-//! - DADK不提供任何开发DragonOS应用所需的API。这部分工作由libc等库来完成
-//!
-//! ## License
-//!
-//! DADK is licensed under the [GPLv2 License](https://www.gnu.org/licenses/old-licenses/gpl-2.0.html).
-//!
-//! ## 快速开始
-//!
-//! ### 安装DADK
-//!
-//! DADK是一个Rust程序,您可以通过Cargo来安装DADK。
-//!
-//! ```shell
-//! # 从GitHub安装最新版
-//! cargo install --git https://github.com/DragonOS-Community/DADK.git
-//!
-//! # 从crates.io下载
-//! cargo install dadk
-//!
-//! ```
-//!
-//! ## DADK的工作原理
-//!
-//! DADK使用(任务名,任务版本)来标识每个构建目标。当使用DADK构建DragonOS应用时,DADK会根据用户的配置文件,自动完成以下工作:
-//!
-//! - 解析配置文件,生成DADK任务列表
-//! - 根据DADK任务列表,进行拓扑排序。这一步会自动处理软件库的依赖关系。
-//! - 收集环境变量信息,并根据DADK任务列表,设置全局环境变量、任务环境变量。
-//! - 根据拓扑排序后的DADK任务列表,自动执行任务。
-//!
-//! ### DADK与环境变量
-//!
-//! 环境变量的设置是DADK能正常工作的关键因素之一,您可以在您的编译脚本中,通过引用环境变量,来获得其他软件库的编译信息。
-//! 这是使得您的应用能够自动依赖其他软件库的关键一步。
-//!
-//! 只要您的编译脚本能够正确地引用环境变量,DADK就能够自动处理软件库的依赖关系。
-//!
-//! DADK会设置以下全局环境变量:
-//!
-//! - `DADK_CACHE_ROOT`:DADK的缓存根目录。您可以在编译脚本中,通过引用该环境变量,来获得DADK的缓存根目录。
-//! - `DADK_BUILD_CACHE_DIR_任务名_任务版本`:DADK的任务构建结果缓存目录。当您要引用其他软件库的构建结果时,可以通过该环境变量来获得。
-//! 同时,您也要在构建您的app时,把构建结果放到您的软件库的构建结果缓存目录(通过对应的环境变量获得)中。
-//! - `DADK_SOURCE_CACHE_DIR_任务名_任务版本`:DADK的某个任务的源码目录。当您要引用其他软件库的源码目录时,可以通过该环境变量来获得。
-//!
-//! 同时,DADK会为每个任务设置其自身在配置文件中指定的环境变量。
-//!
-//! #### 全局环境变量命名格式
-//!
-//! 全局环境变量中的任务名和任务版本,都会被转换为大写字母,并对特殊字符进行替换。替换表如下:
-//!
-//! | 原字符 | 替换字符 |
-//! | ------ | -------- |
-//! | `.`    | `_`      |
-//! | `-`    | `_`      |
-//! | `\t`   | `_`      |
-//! | 空格   | `_`      |
-//! | `+`    | `_`      |
-//! | `*`    | `_`      |
-//!
-//! **举例**:对于任务`libc-0.1.0`,其构建结果的全局环境变量名为`DADK_BUILD_CACHE_DIR_LIBC_0_1_0`。
-//!
-//!
-//! ## TODO
-//!
-//! - 支持从在线归档文件下载源码、构建好的软件库
-//! - 支持自动更新
-//! - 完善clean命令的逻辑
-
-#![feature(extract_if)]
-#![feature(io_error_more)]
-
-#[macro_use]
-extern crate lazy_static;
-extern crate log;
-extern crate serde;
-extern crate serde_json;
-extern crate simple_logger;
-
-use std::{path::PathBuf, process::exit};
-
-use clap::Parser;
-
-use log::{error, info};
-use parser::task::DADKTask;
-use simple_logger::SimpleLogger;
-
-use crate::{
-    console::{interactive::InteractiveConsole, CommandLineArgs},
-    executor::cache::cache_root_init,
-    scheduler::{task_deque::TASK_DEQUE, Scheduler},
-};
-
-mod console;
-mod executor;
-mod parser;
-mod scheduler;
-pub mod static_resources;
-mod utils;
+use dadk::dadk_main;
 
 fn main() {
-    logger_init();
-    // generate_tmp_dadk();
-    info!("DADK Starting...");
-    let args = CommandLineArgs::parse();
-
-    info!("DADK run with args: {:?}", &args);
-    // DragonOS sysroot在主机上的路径
-    let dragonos_dir = args.dragonos_dir.clone();
-    let config_dir = args.config_dir.clone();
-    let action = args.action;
-    let thread = args.thread;
-    info!(
-        "DragonOS sysroot dir: {}",
-        dragonos_dir
-            .as_ref()
-            .map_or_else(|| "None".to_string(), |d| d.display().to_string())
-    );
-    info!(
-        "Config dir: {}",
-        config_dir
-            .as_ref()
-            .map_or_else(|| "None".to_string(), |d| d.display().to_string())
-    );
-    info!("Action: {:?}", action);
-    info!(
-        "Thread num: {}",
-        thread
-            .as_ref()
-            .map_or_else(|| "None".to_string(), |d| d.to_string())
-    );
-
-    match action {
-        console::Action::New => {
-            let r = InteractiveConsole::new(dragonos_dir.clone(), config_dir.clone(), action).run();
-            if r.is_err() {
-                error!("Failed to run interactive console: {:?}", r.unwrap_err());
-                exit(1);
-            }
-            exit(0);
-        }
-        _ => {}
-    }
-
-    if let Some(thread) = thread {
-        TASK_DEQUE.lock().unwrap().set_thread(thread);
-    }
-
-    // 初始化缓存目录
-    let r = cache_root_init(args.cache_dir);
-    if r.is_err() {
-        error!("Failed to init cache root: {:?}", r.unwrap_err());
-        exit(1);
-    }
-
-    let config_dir = args.config_dir.unwrap_or_else(|| {
-        error!("Config dir not specified");
-        exit(1);
-    });
-
-    let dragonos_dir = args.dragonos_dir.unwrap_or_else(|| {
-        error!("DragonOS sysroot dir not specified");
-        exit(1);
-    });
-
-    let mut parser = parser::Parser::new(config_dir);
-    let r = parser.parse();
-    if r.is_err() {
-        exit(1);
-    }
-    let tasks: Vec<(PathBuf, DADKTask)> = r.unwrap();
-    // info!("Parsed tasks: {:?}", tasks);
-
-    let scheduler = Scheduler::new(dragonos_dir, action, tasks);
-    if scheduler.is_err() {
-        exit(1);
-    }
-
-    let r = scheduler.unwrap().run();
-    if r.is_err() {
-        exit(1);
-    }
-}
-
-/// 初始化日志系统
-fn logger_init() {
-    // 初始化日志系统,日志级别为Info
-    // 如果需要调试,可以将日志级别设置为Debug
-    let logger = SimpleLogger::new().with_level(log::LevelFilter::Info);
-
-    logger.init().unwrap();
+    dadk_main();
 }

+ 54 - 0
src/parser/mod.rs

@@ -226,3 +226,57 @@ impl Parser {
         return Ok(task);
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use test_base::{
+        test_context::{self as test_context, test_context},
+        BaseTestContext,
+    };
+    use tests::task::{BuildConfig, TaskType};
+
+    use crate::executor::source::LocalSource;
+
+    use super::*;
+
+    #[test_context(BaseTestContext)]
+    #[test]
+    fn parse_normal_v1(ctx: &mut BaseTestContext) {
+        let parser = Parser::new(ctx.config_v1_dir());
+        let config_file = ctx.config_v1_dir().join("app_normal_0_1_0.dadk");
+        let result = parser.parse_config_file(&config_file);
+
+        assert!(result.is_ok(), "Error: {:?}", result);
+
+        let result = result.unwrap();
+
+        assert_eq!(result.name, "app_normal");
+        assert_eq!(result.version, "0.1.0");
+        assert_eq!(result.description, "A normal app");
+
+        let expected_task_type = TaskType::BuildFromSource(task::CodeSource::Local(
+            LocalSource::new(PathBuf::from("tests/data/apps/app_normal")),
+        ));
+
+        assert_eq!(result.task_type, expected_task_type,);
+
+        assert_eq!(result.depends.len(), 0);
+
+        let expected_build_config: BuildConfig =
+            BuildConfig::new(Some("bash build.sh".to_string()));
+        assert_eq!(result.build, expected_build_config);
+
+        let expected_install_config = task::InstallConfig::new(Some(PathBuf::from("/")));
+
+        assert_eq!(result.install, expected_install_config);
+        let expected_clean_config = task::CleanConfig::new(None);
+
+        assert_eq!(result.clean, expected_clean_config);
+
+        assert!(result.envs.is_some());
+        assert_eq!(result.envs.as_ref().unwrap().len(), 0);
+
+        assert_eq!(result.build_once, false);
+        assert_eq!(result.install_once, false);
+    }
+}

+ 6 - 6
src/parser/task.rs

@@ -206,7 +206,7 @@ impl DADKTask {
 }
 
 /// @brief 构建配置
-#[derive(Debug, Clone, Serialize, Deserialize)]
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
 pub struct BuildConfig {
     /// 构建命令
     pub build_command: Option<String>,
@@ -229,7 +229,7 @@ impl BuildConfig {
     }
 }
 
-#[derive(Debug, Clone, Serialize, Deserialize)]
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
 pub struct InstallConfig {
     /// 安装到DragonOS内的目录
     pub in_dragonos_path: Option<PathBuf>,
@@ -255,7 +255,7 @@ impl InstallConfig {
 }
 
 /// # 清理配置
-#[derive(Debug, Clone, Serialize, Deserialize)]
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
 pub struct CleanConfig {
     /// 清理命令
     pub clean_command: Option<String>,
@@ -312,7 +312,7 @@ impl Dependency {
 }
 
 /// # 任务类型
-#[derive(Debug, Clone, Serialize, Deserialize)]
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 pub enum TaskType {
     /// 从源码构建
     BuildFromSource(CodeSource),
@@ -337,7 +337,7 @@ impl TaskType {
 }
 
 /// # 代码源
-#[derive(Debug, Clone, Serialize, Deserialize)]
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 pub enum CodeSource {
     /// 从Git仓库获取
     Git(GitSource),
@@ -365,7 +365,7 @@ impl CodeSource {
 }
 
 /// # 预编译包源
-#[derive(Debug, Clone, Serialize, Deserialize)]
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 pub enum PrebuiltSource {
     /// 从在线压缩包获取
     Archive(ArchiveSource),

+ 1 - 0
tests/data/apps/app_normal/build.sh

@@ -0,0 +1 @@
+echo "app_normal: build"

+ 25 - 0
tests/data/dadk_config_v1/app_normal_0_1_0.dadk

@@ -0,0 +1,25 @@
+{
+  "name": "app_normal",
+  "version": "0.1.0",
+  "description": "A normal app",
+  "rust_target": null,
+  "task_type": {
+    "BuildFromSource": {
+      "Local": {
+        "path": "tests/data/apps/app_normal"
+      }
+    }
+  },
+  "depends": [],
+  "build": {
+    "build_command": "bash build.sh"
+  },
+  "install": {
+    "in_dragonos_path": "/"
+  },
+  "clean": {
+    "clean_command": null
+  },
+  "envs": [],
+  "build_once": false
+}

+ 21 - 0
tests/parser.rs

@@ -0,0 +1,21 @@
+use log::error;
+use test_base::{
+    test_context::{self as test_context, test_context},
+    BaseTestContext,
+};
+
+const CONFIG_V1_DIR: &str = "tests/data/dadk_config_v1";
+
+#[test_context(BaseTestContext)]
+#[test]
+fn test_parser(ctx: &mut BaseTestContext) {
+    let mut parser = dadk::parser::Parser::new(ctx.abs_path(CONFIG_V1_DIR));
+    let result = parser.parse();
+    let cwd = std::env::current_dir().unwrap();
+
+    log::debug!("Current working directory: {:?}", cwd);
+    if let Err(e) = result {
+        error!("Error: {:?}", e);
+        assert!(false);
+    }
+}