Stephan Avenwedde 发布的文章

观察你的代码在其他解释器下运行的表现或许是一项有趣的尝试。

作为最受欢迎的编程语言之一,Python 需要一个解释器来执行其代码所定义的命令。与其他可直接编译成机器代码的语言不同,Python 代码需要解释器读取它并把它转译给进行相关操作的 CPU。那么,哪些解释器有哪些呢?本文将对其中几种进行介绍。

解释器简介

提到 Python 解释器,我们通常会想到 /usr/bin/python 这个二进制文件。它使你能够执行 .py 文件。然而,解释操作仅仅是其中一环。在 Python 代码真正被 CPU 执行之前,都需要经过以下四个步骤:

  1. 词法分析 - 将人类编写的源代码转换为一序列逻辑实体,被称为 词法标记 lexical token
  2. 解析 - 解析器会检查词法标记的语法和语义规则生成 抽象语法树 abstract syntax tree (AST)。
  3. 编译 - 编译器会根据 AST 创建 Python 字节码,这些字节码由非常基础的,和平台无关的指令组成。
  4. 解释 - 解释器处理字节码并执行特定的操作。

如你所见,在任何实质性的操作发生之前,我们需要走过这些步骤。这也解释了深入研究不同解释器的重要性。

1、CPython

作为 Python 的参考实现,CPython 默认地被许多系统所采用。如其名称所示,CPython 是用 C 语言编写的。这也意味着,我们可以 以 C 语言编写扩展,从而让 Python 打通到广泛使用的 C 语言库代码。CPython 广泛应用于各种平台,包括 ARM 和 RISC。然而,作为 Python 的参考实现,CPython 更注重精细的优化,而非运行速度。

2、Pyston

Pyston 是一个从 CPython 解释器衍生出的分支,其中实现了性能优化。该项目定位自己为标准 CPython 解释器在处理大型、真实世界应用时的替代品,并有可能加速高达 30%。由于缺乏兼容的二进制包,Pyston 在下载过程中需要重新编译。

3、PyPy

采用了 RPython 编写的 PyPy 是一个专为 Python 配备的 即时(JIT) 编译器,RPython 是 Python 的一个静态类型的子集。不同于 CPython 解释器,PyPy 对源代码进行编译,生成 CPU 可直接执行的机器码。PyPy 是 Python 开发者的实验室,在这里他们能更容易地测试新特性。

相较于 CPython,PyPy 的执行速度更快。由于 JIT 编译器的特性,长时间运行的应用更能从缓存中受益。PyPy 可以被视为 CPython 的有效替代。虽然其中存在一些缺点,大部分的 C 扩展模块在 PyPy 中也得到支持,但运行速度会相对慢一些。PyPy 扩展模块使用 Python(而不是 C)编写,这使 JIT 编译器能够对其进行优化。只要你的应用程序不依赖于不兼容的模块,PyPy 就是替换 CPython 的理想选择。你可以在项目官网找到一个专门的页面,详细描述 PyPy 与 CPython 的不同之处:PyPy 与 CPython 的差异

4、RustPython

顾名思义,RustPython 是一个由 Rust 编写的 Python 解释器。尽管 Rust 如今还是一个相对年轻的编程语言,但因其优良特性已逐步受到开发者的推崇,甚至被视为 C 和 C++ 的可能接班人。默认情况下,RustPython 的行为与 CPython 的解释器类似,但它也可以选择启用 JIT 编译器。值得一提的是,Rust 工具链能直接编译为 WebAssembly ,进而允许在浏览器中全面运行解释器。你可以在 这里 看到它的在线演示。

5、Stackless Python

Stackless Python 自称是 Python 编程语言的增强版本。该项目基本上是 CPython 解释器衍生的一个项目,其为该语言添加了微线程、通道和调度器。微线程可以帮助你将代码组织成可以并行运行的 “ 小任务 tasklet ”。这与采用 greenlet 模块的绿色线程模型相似。通道可以用作 “小任务” 之间的双向通信。Stackless Python 的一个知名用户是大型多人在线角色扮演游戏 Eve Online

6、Micro Python

如果你的目标平台是微控制器,那么 MicroPython 将是你的首选。它是一种极简的实现,只需要 16kB 的内存和 256kB 的存储空间。由于其主要面向的是嵌入式环境,MicroPython 的标准库只包含 CPython 丰富的 STL 的一部分。对于开发和测试,或者作为轻量级替代品,MicroPython 也可以在普通的 x86 和 x64 系统上运行。MicroPython 支持 Linux、Windows,以及多种微控制器。

性能

就其设计而言,Python 本质上是一种运行速度不够快的语言。根据任务性质的不同,各种解释器间存在明显的性能差异。要想弄清楚哪种解释器最适合特定任务,可以参考 pybenchmarks.org。与使用解释器相比,另一种选择是直接将 Python 二进制代码编译成机器码,例如,Nuitka 就是能够完成这种工作的项目之一,它可以将 Python 代码编译成 C 代码,然后将 C 代码通过常规的 C 编译器编译成机器码。Python 编译器的主题范围广泛,值得一篇独立的文章来详述。

总结

Python 是构建快速原型和自动化任务的优秀语言,同时它又易于学习,对初学者友好。如果你平时维持使用 CPython,那么尝试看看你的代码在另一解释器上运行会是什么样子也许会很有趣。如果你是 Fedora 用户,你可以轻松地测试几种其他解释器,因为其包管理器已经提供了需要的二进制文件。你可以在 fedora.developer.org 上查找更多信息。

(题图:MJ/9b24f27b-bd2b-4916-9f33-bcfb9e2b1d33)


via: https://opensource.com/article/22/9/python-interpreters-2022

作者:Stephan Avenwedde 选题:lkxed 译者:ChatGPT 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Doxygen 是一款广泛使用的开源文档生成工具,它通过代码注释来生成文档。

在试着熟悉别人的代码时,你总希望他们留下的代码注释能对你理解代码有所帮助。同理,无论为了自己还是其他人,编写代码时写注释是好习惯。所有编程语言都有专门的注释语法,注释可以是一个单词、一行文字、甚至是一整段话。编译器或解释器处理源代码时会忽略注释。

注释不能完全取代文档,但是有方法可以使用注释来生成文档。Doxygen 是一个开源的文档生成工具,它能够根据代码注释生成 HTML 或 LaTeX 格式的文档。Doxygen 让你在不用额外操作的情况下创建代码结构概览。尽管 Doxygen 主要是用来给 C++ 生成文档的,它对其它语言同样适用,比如 C、Objective-C、 C#、 PHP、Java 和 Python 等。

要使用 Doxygen,你只需要在源代码中使用 Doxygen 能够识别的语法来写注释。Doxygen 会扫描源码文件,然后根据这些特殊注释生成 HTML 或 LaTeX 文档。下面的示例项目会演示如何使用 Doxygen 注释,以及文档是如通过注释生成出来的。示例代码可从 GitHub 上获得,本文中也将引用 Doxygen 手册及文档 的相关章节。

在 Linux 上安装 Doxygen

在 Fedora 上可以通过软件包的形式安装 Doxygen。打开终端运行命令:

sudo dnf install doxygen

在基于 Debian 的操作系统上,可以通过以下命令来安装:

sudo apt-get install doxygen

使用

安装完 Doxygen 后,你需要在项目中按 Doxygen 可以识别的格式来注释代码,还要提供一个 Doxyfile 配置文件来控制 Doxygen 的一些行为。

注意:如果你用的是 GitHub 上的示例项目,你可以忽略下面一步。

如果 Doxyfile 文件不存在,你可以用 Doxygen 生成一个标准 Doxyfile 模板文件。切换到项目根目录下,运行:

doxygen -g

参数 -g 表示 生成 generate 。现在应该会出现一个名为 Doxyfile 的新文件。通过命令调用 Doxygen:

doxygen

现在应该能会有两个新文件夹:

  • html/
  • latex/

默认情况下,Doxygen 会同时输出 LaTeX 和 HTML 格式的文档。本文主要关注 HTML 文档。你可以在 Doxygen 官方文档的入门小节中找到关于 LaTeX 格式输出的更多信息。

双击 html/index.html 打开 HTML 文件。用空的配置文件生成的文档如下图:

A screenshot of a doxygen generated main page on Firefox. The content field under My Project Documentation is blank.

现在我们试着修改 Doxyfile 文件,并在源代码中添加特殊注释。

Doxyfile 文件

Doxyfile 文件中可以定义大量的可调选项,本文通过介绍示例项目的 Doxyfile 文件我只能覆盖其中很小的子集。

第 35 行:项目名称

你可以在这里指定项目名称,它最终会显示在 页眉 header 和浏览器标签上。

# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
# double-quotes, unless you are using Doxywizard) that should identify the
# project for which the documentation is generated. This name is used in the
# title of most generated pages and in a few other places.
# The default value is: My Project.

PROJECT_NAME           = "My Project"

第 47 行:项目简介

项目简介会以略小的字号显示在页眉上。

# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
# quick idea about the purpose of the project. Keep the description short.

PROJECT_BRIEF          = "An example of using Doxygen in C++"

第 926 行:包含子目录

允许 Doxygen 查找源代码和文档文件时递归遍历子目录。

# The RECURSIVE tag can be used to specify whether or not subdirectories should
# be searched for input files as well.
# The default value is: NO.

RECURSIVE = YES

第 1769 行:禁用 LaTeX 输出

如果你只想生成 HTML 文档,可以通过这个开关禁用 LaTeX 输出。

# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.

# The default value is: YES.

GENERATE_LATEX = NO

修改完成后,你可以再次运行 Doxygen 来检验修改是否生效了。可以在调用 Doxygen 时使用 -x 选项来查看 Doxyfile 文件的变更项:

A screenshot of the terminal showing the differences, Project Name, Project Brief, Recursive, and status of Generate Latex

通过调用 diff 命令,Doxygen 仅显示当前 Doxyfile 文件和模板文件的差异。

特殊注释

Doxygen 通过扫描源代码文件中的特殊注释和关键字来生成 HTML 文档。示例项目中的 ByteStream 类的头文件可以很好地解释特殊注释的用法。

下面用构造函数和析构函数作为示例:

/*! @brief Constructor which takes an external buffer to operate on
*
* The specified buffer already exist.
* Memory and size can be accessed by buffer() and size().
*
* @param[in] pBuf Pointer to existing buffer
* @param[in] size Size of the existing buffer
*/

ByteStream(char* pBuf, size_t size) noexcept;

特殊注释块有不同的格式风格。我倾向于使用 /*! 开头(Qt 风格),每行前添加 *,以 */ 结束注释块。你可以参考 Doxygen 手册的文档化代码小节,以大致了解不同的风格选项。

Doxygen 注释分两个部分:简要描述和详细描述。它们都是可选的。在上面的例子中的注释块是对紧跟其后的构造函数声明的描述。在 @brief 之后的文本会显示在类概览小节中:

A screenshot of the C++ example of using Doxygen showing the Byte Stream Class Reference. The categories in the list are public member functions, writing (operators for writing to the stream), and reading (operators for reading from the stream)

在空行(空行是段落分隔符)之后是构造函数的实际文档。用 @param[in/out] 关键字标注传递给构造函数的参数,Doxygen 基于此生成参数列表:

Screenshot of the Doxygen example showing the parameters under ByteStream

值得注意的是 Doxygen 为 buffer()size() 方法自动生成了链接。相反,Doxygen 忽略了析构函数前的注释,因为它并没有使用特殊注释:

// Destructor
~ByteStream();

现在你已经看到 Doxygen 的绝大部分功能了。通过使用一种稍微改良的注释格式,让 Doxygen 能够识别它们。通过使用一些关键字,你甚至可以进一步控制格式化。在下一节中,我会进一步介绍 Doxygen 的其它特性。

其它特性

现在几乎所有的工作都可以通过对源代码注释的方式完成。通过一些微调,你可以轻松地优化 Doxygen 的输出。

Markdown 格式

为了进阶的格式化,Doxygen 支持 Markdown 和 HTML 命令。Markdown 速查表可以在 这里 下载到。

项目主页

除了自定义页眉之外,html/index.html 几乎没有其它内容了。你可以通过使用关键字向其中添加一些有意义的内容。因为主页通常不是针对某个源代码文件的,你可以将要显示在主页的内容放到项目根目录下的一个单独文件中。示例项目中就是这样做的,其输出效果如下:

The Doxygen Example Documentation field now contains headings and documentation: Introduction, Running the example, System requirements, and Building the code, with step by step examples and code snippets (all can be found in the example on GitHub)

自动链接生成

上面已将提到了,当你引用代码的其它部分时,Doxygen 会自动识别并生成相应链接。但要注意,这要求被引用部分也有文档才行。

更多信息可以在官方文档的自动链接生成中找到。

分组

ByteStream 重载 overload 了的读写流操作符 (<<>>)。在类的概览中可以发现操作符被分为读和写两组。分组是在 ByteStream 的头文件中定义的。

分组的语法以标记 @{ 开始,以 }@ 结束。在标记范围中的内容都属于这个分组。在 ByteStream.h 中的实现如下:

/** @name Writing
* Operators for writing to the stream
* @{
*/

(...)

/** @}
* @name Reading
* Operators for reading from the stream
* @{
*/

(...)

/** @} */

你可以在官方文档的分组中找到更多相关信息。

LLVM 支持

如果你用 Clang 构建项目的话,可以通过使用 -Wdocumentation 选项让 Clang 对特殊注释进行检查。想了解该特性的更多信息,可以参考 LLVM 用户手册和 Dmitri Gribenko 的展示报告,它们可以在 Clang 网站上找到。

谁在用 Doxygen

Doxygen 是在 1997 年首次发布的。尽管有些年头了,现在仍然有很多项目在使用 Doxygen。比如 NASA 的飞行软件框架 F Prime、图像处理库 OpenCV、包管理器 RPM。你还可以在其它领域发现 Doxygen 语法标记的身影,比如内容管理平台 Drupal 的文档标准中。

注意:Doxygen 输出的 HTML 文档风格类似于九十年代网页。并且它也难以描绘元编程和模板编程架构。在这些情况下,你应该选择 Sphinx 而不是 Doxygen。

(题图:MJ/4d354094-397e-4ac5-a80d-25b9c736ede5)


via: https://opensource.com/article/22/5/document-source-code-doxygen-linux

作者:Stephan Avenwedde 选题:lkxed 译者:toknow-gh 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

连续集成 continuous integration (CI)是指代码变更会被自动构建和测试。以下是我为自己的 C++ 项目构建 CI 流水线的过程。

本文介绍如何在 GitLab 上配置 CI 流水线。我在前面的文章中介绍了 基于 CMake 和 VSCodium 的构建系统基于 GoogleTest 和 CTest 的单元测试。本文将在此基础上进一步配置 CI 流水线。我会先演示如何布设和运行 CI 流水线,然后再介绍如何配置它。

CI 是指提交到代码仓库的代码变更会被自动构建和测试。在开源领域,GitLab 是一个流行的 CI 流水线平台。除了作为中心 Git 仓库外,GitLab 还提供 CI/CD 流水线、 问题跟踪 issue tracking 容器注册表 container registry 功能。

相关术语

在进入正题之前,我先介绍在本文和 GitLab 文档 中会遇到的常见术语。

  • 持续交付 continuous delivery (CD):自动化供应软件,以供随时交付
  • 持续部署 continuous deployment (CD):自动化软件发布
  • 流水线 pipeline : CI/CD 的直接构件,它由阶段和作业构成
  • 阶段 stage :一组作业
  • 作业 job :某项需要执行的具体任务,比如编译、单元测试等
  • 执行器 runner :实际执行作业的服务

布设 CI 流水线

在下面的章节中,我将复用以前的 示例工程。点击 GitLab 仓库页面右上角的 复刻 Fork 按钮复刻代码仓库。

 title=

设置执行器

为了让你对整个流程有所了解,我们先从在本地安装执行器讲起。

参照执行器服务 安装指南 安装好服务,然后注册执行器。

1、选择 GitLab 项目页面左侧的 设置 Settings ,再选择 CI/CD

 title=

2、展开 执行器 Runners 区域,关闭 共享的执行器 Shared runners 选项(黄框处)。特别注意令牌和 URL(绿框处),下一步会用到它们。

 title=

3、在终端中运行 gitlab-runner register,根据提示输入以下注册信息:

  • GitLab 实例: https://gitlab.com/ (如上图)
  • 注册令牌:从执行器区域中获取 (如上图)
  • 描述:按需自由填写
  • 标签:可以不填
  • 执行环境:选 Shell

如果有需要,你可以在 ~/.gitlab-runner/config.toml 中修改这些配置。

4、用命令 gitlab-runner run 启动执行器。你可以在 GitLab 的项目设置界面执行器区域看到执行器的状态:

 title=

运行流水线

前面已经提过,流水线就是一组由执行器执行的作业。每个推送到 GitLab 的提交都会生成一个附加到该提交的流水线。如果多个提交被一起推送,那么只会为最后一个提交生成流水线。为了演示,我直接在 GitLab 在线编辑器中提交和推送修改。

打开 README.md 文件,添加一行数据:

 title=

现在提交修改。

这里注意默认的行为是为提交新建一个分支,为了简便起见,我们择提交到主分支。

 title=

提交后一会儿后,你就应该改能看到 GitLab 执行器执行的控制台中有输出消息:

Checking for jobs... received job=1975932998 repo_url=<https://gitlab.com/hANSIc99/cpp\_testing\_sample.git> runner=Z7MyQsA6

Job succeeded duration_s=3.866619798 job=1975932998 project=32818130 runner=Z7MyQsA6

在 GitLab 项目概览界面左侧选择 CI/CD --> 管道 Pipelines ,查看最近执行的流水线:

 title=

选中流水线可以在详情界面看到哪些作业失败了,并能查看各个作业的输出。

当遇到非零返回值是就认为作业执行失败了。在下面的例子中我通过调用 exit 1 强制让作业执行失败:

 title=

CI 配置

阶段、流水线和作业的配置都在仓库根目录的 .gitlab-ci.yml 文件中。我建议使用 GitLab 内置的流水线编辑器,它会自动对配置进行检查。

stages:
- build
- test

build:
  stage: build
  script:
    - cmake -B build -S .
    - cmake --build build --target Producer
  artifacts:
    paths:
      - build/Producer

RunGTest:
  stage: test
  script:
    - cmake -B build -S .
    - cmake --build build --target GeneratorTest
    - build/Generator/GeneratorTest

RunCTest:
  stage: test
  script:
    - cmake -B build -S .
    - cd build
    - ctest --output-on-failure -j6

文件中定义了两个阶段:buildtest,以及三个作业:buildRunGTestRunCTest。其中作业 build 属于一个同名的阶段,另外两个作业属于阶段 test

script 小节下的命令就是一般的 Shell 命令。你可以认为是将它们逐行输入到 Shell 中。

我要特别提及 产物artifact 这个特性。在示例中我定义了二进制的 Producer 为作业 build 的产物。产物会被上传到 GitLab 服务器,并且可以从服务器的这个页面上被下载:

 title=

默认情况下,后续阶段的作业会自动下载先前阶段作业生成的所有产物。

你可以在 docs.gitlab.com 上查看 gitlab-ci.yml 参考指南。

总结

上面只是一个最基本的例子,让你对持续集成的一般原则有一个了解。再演示中我禁用了共享执行器,然而这才是 GitLab 的优势所在。你可以在一个干净的容器化的环境中构架、测试和部署程序。除了使用 GitLab 提供的免费执行器,你也可以用自己的容器作为执行器。当然还有更高阶的用法:用 Kubernetes 来协调调度执行者容器,让流水线适应大规模使用的使用场景。如需进一步了解,可以查看 about.gitlab.com

如果你使用的是 Fedora,需要注意的一点是目前 GitLab 执行者还不支持用 Podman 作为容器引擎。(LCTT 译注:Podman 是 Fedora 自带的容器引擎。)根据 议题 issue #27119,对 Podman 支持已将列上日程。(LCTT 译注:Podman 4.2 及以上版本增加了对于 GitLab 执行器的支持。)

把重复性的操作描述成作业,并将作业合并成流水线和阶段,可以让你跟踪它们的质量而不增加额外工作。。特别是在大型社区项目中,适当配置的 CI 可以告诉你提交的代码是否对项目有改善,为你接受或拒绝合并请求提供依据。

(题图:MJ/fb711c48-251a-4726-a41c-247370e5df25)


via: https://opensource.com/article/22/2/setup-ci-pipeline-gitlab

作者:Stephan Avenwedde 选题:lujun9972 译者:toknow-gh 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

进行单元测试可以提高代码质量,并且它不会打断你的工作流。

 title=

本文是 使用 CMake 和 VSCodium 设置一个构建系统 的后续文章。

在上一篇文章中我介绍了基于 VSCodiumCMake 配置构建系统。本文我将介绍如何通过 GoogleTestCTest 将单元测试集成到这个构建系统中。

首先克隆 这个仓库,用 VSCodium 打开,切换到 devops_2 标签。你可以通过点击 main 分支符号(红框处),然后选择 devops_2 标签(黄框处)来进行切换:

 title=

或者你可以通过命令行来切换:

$ git checkout tags/devops_2

GoogleTest

GoogleTest 是一个平台无关的开源 C++ 测试框架。单元测试是用来验证单个逻辑单元的行为的。尽管 GoogleTest 并不是专门用于单元测试的,我将用它对 Generator 库进行单元测试。

在 GoogleTest 中,测试用例是通过断言宏来定义的。断言可能产生以下结果:

  • 成功: 测试通过。
  • 非致命失败: 测试失败,但测试继续。
  • 致命失败: 测试失败,且测试终止。

致命断言和非致命断言通过不同的宏来区分:

  • ASSERT_*: 致命断言,失败时终止。
  • EXPECT_*: 非致命断言,失败时不终止。

谷歌推荐使用 EXPECT_* 宏,因为当测试中包含多个的断言时,它允许继续执行。断言有两个参数:第一个参数是测试分组的名称,第二个参数是测试自己的名称。Generator 只定义了 generate(...) 函数,所以本文中所有的测试都属于同一个测试组:GeneratorTest

针对 generate(...) 函数的测试可以从 GeneratorTest.cpp 中找到。

引用一致性检查

generate(...) 函数有一个 std::stringstream 的引用作为输入参数,并且它也将这个引用作为返回值。第一个测试就是检查输入的引用和返回的引用是否一致。

TEST(GeneratorTest, ReferenceCheck){
    const int NumberOfElements = 10;
    std::stringstream buffer;
    EXPECT_EQ(
        std::addressof(buffer),
        std::addressof(Generator::generate(buffer, NumberOfElements))
    );
}

在这个测试中我使用 std::addressof 来获取对象的地址,并用 EXPECT_EQ 来比较输入对象和返回对象是否是同一个。

检查元素个数

本测试检查作为输入的 std::stringstream 引用中的元素个数与输入参数中指定的个数是否相同。

TEST(GeneratorTest, NumberOfElements){
    const int NumberOfElements = 50;
    int nCalcNoElements = 0;

    std::stringstream buffer;

    Generator::generate(buffer, NumberOfElements);
    std::string s_no;

    while(std::getline(buffer, s_no, ' ')) {
        nCalcNoElements++;
    }

    EXPECT_EQ(nCalcNoElements, NumberOfElements);
}

乱序重排

本测试检查随机化引擎是否工作正常。如果连续调用两次 generate 函数,应该得到的是两个不同的结果。

TEST(GeneratorTest, Shuffle){

    const int NumberOfElements = 50;

    std::stringstream buffer_A;
    std::stringstream buffer_B;

    Generator::generate(buffer_A, NumberOfElements);
    Generator::generate(buffer_B, NumberOfElements);

    EXPECT_NE(buffer_A.str(), buffer_B.str());
}

求和校验

与前面的测试相比,这是一个大体量的测试。它检查 1 到 n 的数值序列的和与乱序重排后的序列的和是否相等。 generate(...) 函数应该生成一个 1 到 n 的乱序的序列,这个序列的和应当是不变的。

TEST(GeneratorTest, CheckSum){

    const int NumberOfElements = 50;
    int nChecksum_in = 0;
    int nChecksum_out = 0;

    std::vector<int> vNumbersRef(NumberOfElements); // Input vector
    std::iota(vNumbersRef.begin(), vNumbersRef.end(), 1); // Populate vector

    // Calculate reference checksum
    for(const int n : vNumbersRef){
        nChecksum_in += n;
    }

    std::stringstream buffer;
    Generator::generate(buffer, NumberOfElements);

    std::vector<int> vNumbersGen; // Output vector
    std::string s_no;

    // Read the buffer back back to the output vector
    while(std::getline(buffer, s_no, ' ')) {
        vNumbersGen.push_back(std::stoi(s_no));
    }

    // Calculate output checksum
    for(const int n : vNumbersGen){
        nChecksum_out += n;
    }

    EXPECT_EQ(nChecksum_in, nChecksum_out);
}

你可以像对一般 C++ 程序一样调试这些测试。

CTest

除了嵌入到代码中的测试之外,CTest 提供了可执行程序的测试方式。简而言之就是通过给可执行程序传入特定的参数,然后用 正则表达式 对它的输出进行匹配检查。通过这种方式可以很容易检查程序对于不正确的命令行参数的反应。这些测试定义在顶层的 CMakeLists.txt 文件中。下面我详细介绍 3 个测试用例:

参数正常

如果输入参数是一个正整数,程序应该输出应该是一个数列:

add_test(NAME RegularUsage COMMAND Producer 10)
set_tests_properties(RegularUsage
    PROPERTIES PASS_REGULAR_EXPRESSION "^[0-9 ]+"
)

没有提供参数

如果没有传入参数,程序应该立即退出并提示错误原因:

add_test(NAME NoArg COMMAND Producer)
set_tests_properties(NoArg
    PROPERTIES PASS_REGULAR_EXPRESSION "^Enter the number of elements as argument"
)

参数错误

当传入的参数不是整数时,程序应该退出并报错。比如给 Producer 传入参数 ABC

add_test(NAME WrongArg COMMAND Producer ABC)
set_tests_properties(WrongArg
    PROPERTIES PASS_REGULAR_EXPRESSION "^Error: Cannot parse"
)

执行测试

可以使用 ctest -R Usage -VV 命令来执行测试。这里给 ctest 的命令行参数:

  • -R <测试名称> : 执行单个测试
  • -VV:打印详细输出

测试执行结果如下:

$ ctest -R Usage -VV
UpdatecTest Configuration from :/home/stephan/Documents/cpp_testing sample/build/DartConfiguration.tcl
UpdateCTestConfiguration from :/home/stephan/Documents/cpp_testing sample/build/DartConfiguration.tcl
Test project /home/stephan/Documents/cpp_testing sample/build
Constructing a list of tests
Done constructing a list of tests
Updating test list for fixtures
Added 0 tests to meet fixture requirements
Checking test dependency graph...
Checking test dependency graph end

在这里我执行了名为 Usage 的测试。

它以无参数的方式调用 Producer

test 3
    Start 3: Usage
3: Test command: /home/stephan/Documents/cpp testing sample/build/Producer

输出不匹配 [^[0-9]+] 的正则模式,测试未通过。

3: Enter the number of elements as argument
1/1 test #3. Usage ................

Failed Required regular expression not found.
Regex=[^[0-9]+]

0.00 sec round.

0% tests passed, 1 tests failed out of 1
Total Test time (real) =
0.00 sec
The following tests FAILED:
3 - Usage (Failed)
Errors while running CTest
$

如果想要执行所有测试(包括那些用 GoogleTest 生成的),切换到 build 目录中,然后运行 ctest 即可:

 title=

在 VSCodium 中可以通过点击信息栏的黄框处来调用 CTest。如果所有测试都通过了,你会看到如下输出:

 title=

使用 Git 钩子进行自动化测试

目前为止,运行测试是开发者需要额外执行的步骤,那些不能通过测试的代码仍然可能被提交和推送到代码仓库中。利用 Git 钩子 可以自动执行测试,从而防止有瑕疵的代码被提交。

切换到 .git/hooks 目录,创建 pre-commit 文件,复制粘贴下面的代码:

#!/usr/bin/sh

(cd build; ctest --output-on-failure -j6)

然后,给文件增加可执行权限:

$ chmod +x pre-commit

这个脚本会在提交之前调用 CTest 进行测试。如果有测试未通过,提交过程就会被终止:

 title=

只有所有测试都通过了,提交过程才会完成:

 title=

这个机制也有一个漏洞:可以通过 git commit --no-verify 命令绕过测试。解决办法是配置构建服务器,这能保证只有正常工作的代码才能被提交,但这又是另一个话题了。

总结

本文提到的技术实施简单,并且能够帮你快速发现代码中的问题。做单元测试可以提高代码质量,同时也不会打断你的工作流。GoogleTest 框架提供了丰富的特性以应对各种测试场景,文中我所提到的只是一小部分而已。如果你想进一步了解 GoogleTest,我推荐你阅读 GoogleTest Primer

(题图:MJ/f212ce43-b60b-4005-b70d-8384f2ba5860)


via: https://opensource.com/article/22/1/unit-testing-googletest-ctest

作者:Stephan Avenwedde 选题:lujun9972 译者:toknow-gh 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

OpenWrt 是一个基于 Linux 的开源操作系统,主要针对嵌入式网络设备。

如果你在家里阅读这篇文章,你可能是用一个 LTE/5G/DSL/WIFI 路由器联网的。这种设备通常负责在你的本地设备(智能手机、PC、电视等)之间路由数据包,并通过内置的调制解调器提供对 WWW 的访问。你家里的路由器很可能有一个基于网页的界面,用于配置该设备。这种界面往往过于简单,因为它们是为普通用户制作的。

如果你想要更多的配置选项,但又不想花钱买一个专业的设备,你应该看看其他的固件,如 OpenWrt

OpenWrt 的特点

OpenWrt 是一个基于 Linux 的、针对嵌入式网络设备的开源操作系统。它主要用于替代各种家用路由器上的原始固件。OpenWrt 具备一个好的路由器应该具备的所有有用功能,如 DNS 服务器(dnsmasq),WiFi 接入点(AP)和客户端功能,用于调制解调器功能的 PPP 协议,而且,与标准固件不同,这一切都是可以完全配置的。

LuCI 网页界面

OpenWrt 可以通过命令行(SSH)或使用 GUI 配置界面(LuCI)进行远程配置。LuCI 是一个用 Lua 编写的轻量级、可扩展的网页 GUI,它可以精确地配置你的设备。除了配置,LuCI 还提供了很多额外的信息,如实时图表、系统日志和网络诊断。

LuCI 网页界面

LuCI 有一些可选的扩展,以增加更多的配置选择。

可写文件系统

它的另一个亮点是可写文件系统。原有的固件通常是只读的,而 OpenWrt 配备了一个可写的文件系统,这要归功于一个巧妙的解决方案,它将 OverlayFS 与 SquashFS/JFFS2 文件系统相结合,允许安装软件包以增强功能。在 OpenWrt 文档 中可以找到更多关于文件系统架构的信息。

扩展

OpenWrt 有一个相关的软件包管理器,opkg,它允许安装额外的服务,比如 FTP 服务器、DLNA 媒体服务器、OpenVPN 服务器、用于实现文件共享的 Samba 服务器、控制电话的 Asterisk 等等。当然,有些扩展需要适当的底层硬件资源。

动机

你可能想知道为什么要冒着对你的设备造成不可修复的损害和失去保修的风险,而尝试更换路由器制造商的固件。如果你的设备以你想要的方式工作,那么你可能不应该。永远不要碰一个正在运行的系统!但是,如果你想增强功能,或者你的设备缺乏配置选项,那么你应该看看 OpenWrt 是否可以成为一种补救措施。

在我的例子中,我想要一个旅行用的路由器,当我在露营地的时候,我可以把它放在一个合适的位置,以便让其它设备与这个本地 WiFi 接入点(AP)保持良好连接。该路由器将作为一个普通的客户端连接到互联网,并广播它的 WiFi 接入点让我的其它设备连接到它。这样我就可以配置我的所有设备与这个路由器的接入点连接,当我在其他地方时我只需要改变路由器的客户端连接。此外,在一些露营地,你只能得到一个单一设备的访问代码,我可以通过这种设置来加强。

作为我的旅行路由器,我选择 TP-Link TL-WR902AC 的原因如下:

  • 很小
  • 两根 WiFi 天线
  • 5V 电源(USB)
  • 低功耗
  • 成本效益高(你以 30 美元左右的价格得到它)

为了了解它的尺寸,这里是它在树莓派 4 旁边的样子:

TP-Link TL-WR902AC 在树莓派旁边

尽管这个路由器带来了我所需要的所有硬件功能,但我很快发现,默认的固件并不能让我按照我想要的方式配置它。该路由器主要是作为一个 WiFi 接入点,它可以复制现有的 WiFi 网络或通过板载以太网接口将自己连接到网络。默认的固件对于这些使用情况是非常有限的。

(LCTT 译注:此型号国内没有销售,它的特点之一是可以通过插入 3G/4G USB 网卡连接到互联网,但由于它不在国内销售,所以没有支持哪种国内 3G/4G USB 网卡的说明,我 查下来 似乎华为的 E3372h-320 是可用的。有相关实践的同学可以分享一下经验。

国内销售的其它类似型号只能通过以太网口或 WiFi 连接到互联网,这种情况下,如果只能通过 3G/4G 连接互联网,那需要另外买一个随身 WiFi /移动路由器。)

幸运的是,该路由器能够运行 OpenWrt,所以我决定用它来替换原来的固件。

安装

当你的 LTE/5G/DSL/WiFi 路由器符合 最低要求 时,很有可能在它上面运行 OpenWrt。下一步,你要查看 硬件表,检查你的设备是否被列为兼容,以及你要选择哪个固件包。OpenWrt 的 TP-Link TL-WR902AC 的页面还包括安装说明,其中描述了如何刷入内部存储器。

刷入固件的过程在不同的设备之间可能会有所不同,所以我就不详细介绍了。简而言之,我必须通过将设备连接到一个具有特定 IP 地址的网络接口上的 TFTP 服务器,重命名 OpenWrt 固件文件,然后按复位按钮启动设备。

配置

一旦刷入成功,你的设备现在应该用新的固件启动了。现在启动可能需要更长的时间,因为与默认固件相比,OpenWrt 具有更多的功能。

为了开始配置,需要在你的 PC 和路由器之间建立一个直接的以太网连接,OpenWrt 在此充当了一个 DHCP 服务器,并将你的 PC 的以太网适配器配置为一个 DHCP 客户端。

在 Fedora Linux 上,要激活你的网络适配器的 DHCP 客户端模式,首先你必须通过运行找出连接的 UUID:

$ nmcli connection show
NAME          UUID         TYPE      DEVICE 
Wired Conn 1  7a96b...27a  ethernet  ens33
virbr0        360a0...673  bridge   virbr0
testwifi      2e865...ee8  wifi     --
virbr0        bd487...227  bridge   --
Wired Conn 2  16b23...7ba  ethernet --

选择你要修改的连接的 UUID,然后运行:

$ nmcli connection modify <UUID> ipv4.method auto

你可以在 Fedora 联网维基 中找到更多关于这些命令的信息。

在你连接到路由器后,打开一个网页浏览器并导航到 http://openwrt/。现在你应该看到 LuCI 的登录管理器:

LuCI 登录

使用 root 作为用户名,并将密码留空。

配置 WiFi 和路由

要配置你的 WiFi 天线,请点击 “ 网络 Network ” 菜单并选择 “ 无线 Wireless ”。

LuCI 无线配置

在我的设备上,上面的天线 radio0 工作在 2.4GHz 模式,并连接到名为 MOBILE-INTERNET 的本地接入点。下面的天线 radio1 工作在 5GHz,有一个相关的接入点,SSID 为 OpenWrt_AV。通过点击 “ 编辑 Edit ” 按钮,你可以打开设备配置,以决定该设备属于 LAN 还是 WWAN 网络。在我的例子中,接入点 OpenWrt_AV 属于 LAN 网络,客户端连接 MOBILE-INTERNET 属于 WWAN 网络。

LuCI 配置屏幕

配置的网络在 “ 接口 Interfaces ” 面板的 “ 网络 Network ” 下列出。

设备列表

为了获得我想要的功能,网络流量必须在 LAN 和 WWAN 网络之间进行路由。路由可以在 “ 网络 Network ” 面板的 “ 防火墙 Firewall ” 部分进行配置。我没有在这里做任何改动,因为在默认情况下,网络之间的流量是被路由的,而传入的数据包(从 WWAN 到 LAN)必须通过防火墙。

防火墙设置

因此,你需要知道的是一个接口是属于 LAN 还是 (W)WAN。这个概念使它相对容易配置,特别是对初学者来说。你可以在 OpenWrt 联网基础 指南中找到更多信息。

专属门户

公共 WiFi 接入点通常受到 专属门户 的保护,你必须输入一个访问代码或类似的代码。通常情况下,当你第一次连接到接入点并试图打开一个任意的网页时,这种门户就会出现。这种机制是由接入点的 DNS 服务器实现的。

默认情况下,OpenWrt 激活了一个安全功能,可以防止连接的客户端受到 DNS 重新绑定攻击。OpenWrt 的重新绑定保护也阻止了专属门户网站被转发到客户端,所以你必须禁用重新绑定保护,以便你可以到达专属门户网站。这个选项在 “ 网络 Network ” 菜单的 “ DHCP 和 DNS DHCP and DNS ” 面板中。

尝试 OpenWrt

由于升级到 OpenWrt,我得到了一个基于商品硬件的灵活的旅行路由器。OpenWrt 使你的路由器具有完全的可配置性和可扩展性,而且由于其制作精良的网页 GUI,它也适合初学者使用。甚至有一些 精选路由器 在出厂时已经安装了 OpenWrt。你还可以用很多 可用的软件包 来增强你的路由器的功能。例如,我正在使用 vsftp FTP 服务器,在连接的 U 盘上托管一些电影和电视剧。看看该 项目主页,在那里你可以找到许多切换到 OpenWrt 的理由。

图片来自: Stephan Avenwedde,CC BY-SA 4.0


via: https://opensource.com/article/22/7/openwrt-open-source-firmware

作者:Stephan Avenwedde 选题:lkxed 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

多测量几次总比测量一次好。我掉到坑里,希望你可以不用。

我想写一篇文章来演示“如何使用树莓派实现莫某的自动化”,或围绕树莓派的其他一些有趣、好奇或有用的应用。正如你可能从标题中意识到的那样,我不能再提供这样的文章了,因为我毁了我心爱的树莓派。

树莓派是每个技术爱好者办公桌上的标准设备。因此,大量教程和文章告诉你可以用它做什么。这篇文章反而涵盖了阴暗面:我描述了你最好不要做的事情!

电缆颜色

在谈到实际的破坏点之前,我想提供一些背景。在房屋内外进行电气工作时,你必须处理不同颜色的电缆。在德国,每栋房子都连接到三相交流电网,你通常会发现以下电缆颜色:

  • 零线:蓝色
  • (PE)地线:黄绿色
  • (L1)火线 1:棕色
  • (L2)火线 2:黑色
  • (L3)火线 3:灰色

例如,给灯接线时,你接零线(N,蓝色)和火线(L,有 1/3 的机会是棕色),它们之间的电压为 230V 交流电。

连接树莓派

今年早些时候,我写了一篇关于 OpenWrt,家用路由器固件的开源替代品 的文章。在文章中,我使用了 TP-link 路由器设备。但是,最初的计划是使用我的树莓派 4。

OpenWrt and Raspberry Pi comparison

我的想法是构建一个旅行路由器,我可以将其安装在我的大篷车中以改善露营地的互联网连接(我是那种离不开互联网的露营者)。为此,我在我的树莓派中添加了一个单独的 USB 无线网卡以连接第二个 Wifi 天线并安装了 OpenWrt。此外,我添加了一个 12V 至 5V DC/DC 转换器来连接大篷车中的 12V 接线。我用桌上的 12V 汽车电池测试了这个设置,它按预期工作。一切设置和配置完成后,我开始将其安装到我的大篷车中。

在我的大篷车里,我找到了一根蓝色和一根棕色的电线,将它与 12V 至 5V DC/DC 转换器相连,将保险丝放回,然后……

DC converter device

这个芯片,自己爆开了,它才是真正的降压变压器。我非常自信地认为蓝线是在 0V 电位上,棕色的是在 12V 上,我甚至没有测量。后来我了解到,蓝色的线是在 12V 上,而棕色的线是接地(这在汽车电子产品中很常见)。

总结

自从这次事故后,我的树莓派就再也启动不起来了。由于树莓派的价格飞涨,我不得不寻找替代品。幸运的是,我遇到了 TP-Link 旅行路由器,它也可以运行 Open-WRT 并且可以令人满意地完成它的工作。

最后:多测量几次总比测量一次好。


via: https://opensource.com/article/23/3/how-i-destroyed-my-raspberry-pi

作者:Stephan Avenwedde 选题:lkxed 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出