分类 软件开发 下的文章

通过突变测试来修复未知的 bug。

你一定对所有内容都进行了测试,也许你甚至在项目仓库中有一个徽章,标明有 100% 的测试覆盖率,但是这些测试真的帮到你了吗?你怎么知道的?

开发人员很清楚单元测试的成本。测试必须要编写。有时它们无法按照预期工作:存在假告警或者抖动测试。在不更改任何代码的情况下有时成功,有时失败。通过单元测试发现的小问题很有价值,但是通常它们悄无声息的出现在开发人员的机器上,并且在提交到版本控制之前就已得到修复。但真正令人担忧的问题大多是看不见的。最糟糕的是,丢失的告警是完全不可见的:你看不到没能捕获的错误,直到出现在用户手上 —— 有时甚至连用户都看不到。

有一种测试可以使不可见的错误变为可见: 突变测试 mutation testing

变异测试通过算法修改源代码,并检查每次测试是否都有“变异体”存活。任何在单元测试中幸存下来的变异体都是问题:这意味着对代码的修改(可能会引入错误)没有被标准测试套件捕获。

Python 中用于突变测试的一个框架是 mutmut

假设你需要编写代码来计算钟表中时针和分针之间的角度,直到最接近的度数,代码可能会这样写:

def hours_hand(hour, minutes):
    base = (hour % 12 ) * (360 // 12)
    correction = int((minutes / 60) * (360 // 12))
    return base + correction

def minutes_hand(hour, minutes):
    return minutes * (360 // 60)

def between(hour, minutes):
    return abs(hours_hand(hour, minutes) - minutes_hand(hour, minutes))

首先,写一个简单的单元测试:

import angle

def test_twelve():
    assert angle.between(12, 00) == 0

足够了吗?代码没有 if 语句,所以如果你查看覆盖率:

$ coverage run `which pytest`
============================= test session starts ==============================
platform linux -- Python 3.8.3, pytest-5.4.3, py-1.8.2, pluggy-0.13.1
rootdir: /home/moshez/src/mut-mut-test
collected 1 item                                                              

tests/test_angle.py .                                                    [100%]

============================== 1 passed in 0.01s ===============================

完美!测试通过,覆盖率为 100%,你真的是一个测试专家。但是,当你使用突变测试时,覆盖率会变成多少?

$ mutmut run --paths-to-mutate angle.py
<snip>
Legend for output:
? Killed mutants.   The goal is for everything to end up in this bucket.
⏰ Timeout.          Test suite took 10 times as long as the baseline so were killed.
? Suspicious.       Tests took a long time, but not long enough to be fatal.
? Survived.         This means your tests needs to be expanded.
? Skipped.          Skipped.
<snip>
⠋ 21/21  ? 5  ⏰ 0  ? 0  ? 16  ? 0

天啊,在 21 个突变体中,有 16 个存活。只有 5 个通过了突变测试,但是,这意味着什么呢?

对于每个突变测试,mutmut 会修改部分源代码,以模拟潜在的错误,修改的一个例子是将 > 比较更改为 >=,查看会发生什么。如果没有针对这个边界条件的单元测试,那么这个突变将会“存活”:这是一个没有任何测试用例能够检测到的潜在错误。

是时候编写更好的单元测试了。很容易检查使用 results 所做的更改:

$ mutmut results
<snip>
Survived ? (16)

---- angle.py (16) ----

4-7, 9-14, 16-21
$ mutmut apply 4
$ git diff
diff --git a/angle.py b/angle.py
index b5dca41..3939353 100644
--- a/angle.py
+++ b/angle.py
@@ -1,6 +1,6 @@
 def hours_hand(hour, minutes):
     hour = hour % 12
-    base = hour * (360 // 12)
+    base = hour / (360 // 12)
     correction = int((minutes / 60) * (360 // 12))
     return base + correction

这是 mutmut 执行突变的一个典型例子,它会分析源代码并将运算符更改为不同的运算符:减法变加法。在本例中由乘法变为除法。一般来说,单元测试应该在操作符更换时捕获错误。否则,它们将无法有效地测试行为。按照这种逻辑,mutmut 会遍历源代码仔细检查你的测试。

你可以使用 mutmut apply 来应用失败的突变体。事实证明你几乎没有检查过 hour 参数是否被正确使用。修复它:

$ git diff
diff --git a/tests/test_angle.py b/tests/test_angle.py
index f51d43a..1a2e4df 100644
--- a/tests/test_angle.py
+++ b/tests/test_angle.py
@@ -2,3 +2,6 @@ import angle
 
 def test_twelve():
     assert angle.between(12, 00) == 0
+
+def test_three():
+    assert angle.between(3, 00) == 90

以前,你只测试了 12 点钟,现在增加一个 3 点钟的测试就足够了吗?

$ mutmut run --paths-to-mutate angle.py
<snip>
⠋ 21/21  ? 7  ⏰ 0  ? 0  ? 14  ? 0

这项新测试成功杀死了两个突变体,比以前更好,当然还有很长的路要走。我不会一一解决剩下的 14 个测试用例,因为我认为模式已经很明确了。(你能将它们降低到零吗?)

变异测试和覆盖率一样,是一种工具,它允许你查看测试套件的全面程度。使用它使得测试用例需要改进:那些幸存的突变体中的任何一个都是人类在篡改代码时可能犯的错误,以及潜伏在程序中的隐藏错误。继续测试,愉快地搜寻 bug 吧。


via: https://opensource.com/article/20/7/mutmut-python

作者:Moshe Zadka 选题:lujun9972 译者:MjSeven 校对:wxy

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

只需一点编程经验,你就可以在短短几天内(有时更少)学会一种新编程语言。

有些人喜欢学习新的编程语言,也有一些人觉得学习一种都是可望不可及的事情。在本文中,我将向你展示如何像程序员一样思考,这样你就可以自信地学习任何一门你想要学习的编程语言。

事实上,一旦你学会了如何编程,你使用的编程语言就不再是一个障碍,而更像是一种形式。实际上,这就是教育家们倡导 让孩子尽早学习编程 的众多原因之一。不管他们的入门语言有多简单,这种编程的逻辑和儿童们(或成人学习者)以后可能遇到的其他语言的逻辑有着想通之处。

只需有一点编程经验(你可以从我们这里的几篇介绍性文章中获得),你就可以在短短几天内(有时更短)学习任何编程语言。这并不是魔法,你也确实要为此付出一些努力。诚然,学习一种编程语言每个的可用库,或者学习打包代码以及进行交付的细微差别,需要的时间远远不止几天。但是,就入门来说,比你想像中的要容易许多,剩下的则要通过不断练习来完成。

当有经验的程序员静下心来学习一门新的编程语言时,他们会寻找五样东西。只要你知道了这五件事,你就可以开始编码了。

1、语法

 title=

语言的语法描述了代码的结构。这包括如何逐行编写代码,以及用于构造代码语句的实际单词。

例如,Python 以使用缩进来指示一个代码块在哪里结束以及另一代码块在哪里开始而闻名:

while j < rows:
    while k < columns:
        tile = Tile(k * w)
        board.add(tile)
        k += 1
    j += 1
    k = 0

Lua 只是使用关键字 end

for i,obj in ipairs(hit) do
  if obj.moving == 1 then
     obj.x,obj.y = v.mouse.getPosition()
  end
end

JavaC、C++ 之类的编程语言使用花括号:

while (std::getline(e,r)) {
  wc++;
  }

编程语言的语法还包括包括库、设置变量和终止行等内容。通过练习,你将学会在阅读示例代码时下意识地识别语法需求(和惯例)。

实践

当学习一门新的编程语言时,要努力理解它的语法。你不需要去记住它,只需要知道如果忘记了以后去哪里查找。使用好的 IDE 也很有帮助,因为很多 IDE 在出现语法错误时会提醒你。

2、内置函数和条件

 title=

就像自然语言一样,编程语言可以识别的合法单词是有限的。这个词汇表可以使用其他库进行扩展,但是核心语言知道一组特定的关键字。大多数语言并没有你想的那么多关键字。即使在像 C 语言这样非常低级的语言中,也只有 32 个关键字,比如 fordowhileintfloatcharbreak 等等。

了解了这些关键字,你就可以编写基本的表达式,也就是构建程序的代码块。许多内置的关键字能帮助你构建条件语句,这些条件语句影响整个程序的流程。例如,如果你想编写一个允许单击和拖动图标的程序,那么你的代码就必须检测用户的鼠标指针何时位于图标上。只有当鼠标光标位于图标外部边缘相同的坐标时,才执行导致使鼠标抓取图标的代码。这是一个经典的 if / then 语句,但不同的语言可以用不同的方式表达。

Python 使用 ifelifelse 的组合来实现条件语句,但是并不显式的关闭语句:

if var == 1:
    # action
elif var == 2:
    # some action
else:
    # some other action

Bash 使用 ifelifelse,并且使用 fi 来结束语句:

if [ "$var" = "foo" ]; then
   # action
elif [ "$var" = "bar" ]; then
   # some action
else
   # some other action
fi

然而 C 和 Java, 使用 ifelseelse if,用花括号把它们括起来:

if (boolean) {
   // action
} else if (boolean) {
   // some action
} else {
   // some other action
}

各种编程语言虽然在关键字的选择和语法上有细微的变化,但基本是相同的。学习如何在编程语言中定义条件语句,包括 if / thendo...whilecase 语句。

实践

要熟悉编程语言能够理解的关键字集。在实践中,你的代码将不仅仅包含编程语言的关键字,可以肯定的是,有包含很多简单函数的库来帮助你做一些事情,诸如将输出打印到屏幕或显示窗口之类。然而,驱动这些库的逻辑始于编程语言的内置关键字。

3、数据类型

 title=

代码是用来处理数据的,因此你必须学习编程语言如何识别不同类型的数据。所有编程语言都能理解整数,大多数的语言能理解小数和单个字符(abc 等等)。它们通常被表示为 intfloatdoublechar,当然,语言的内置词汇表会告诉你如何引用这些实体。

有时候,在编程语言中内置了一些额外的数据类型,也有时是通过引用库来启用复杂的数据类型。例如,Python 可以识别关键字为 str 的字符串,但是 C 语言的代码中必须包含 string.h 头文件才能实现字符串特性。

实践

库可以为你的代码解锁各种类型的数据,但是学习编程语言中包含的基本数据类型是一个明智的起点。

4、运算符和解析器

 title=

一旦你理解了编程语言可处理的数据类型,就可以学习如何分析这些数据了。幸运的是,数学这门学科是相当稳定的,所以算数运算符在许多语言中通常是相同的(或至少非常相似)。例如,两个整数相加通常用 + 符号完成,而测试一个整数是否大于另一个整数通常用 > 符号完成。测试是否相等通常使用 == 来完成(是的,是两个等号,因为通常一个等号用来赋值)。

当然也有一些例外,比如像 Lisp 和 Bash 语言算数运算符就不是如此,但与其他语言一样,这只是一个心理翻译的问题。一旦你了解了表达方式有何不同,很快就可以适应它。快速浏览一下一门编程语言的算数运算符通常足以让你了解算数操作是如何完成的。

你还需要知道如何比较和操作非数值数据,比如字符和字符串。这些通常是通过编程语言的核心库来进行的的。例如,Python 提供了 split() 方法,而 C 语言需要引入头文件 string.h 来提供 strtok() 函数。

实践

了解用于处理基本数据类型的基本函数和关键字,并寻找可帮助你完成复杂操作的核心库。

5、函数

 title=

代码不只是计算机的待办清单。通常情况下,在编写代码时你往往希望向计算机提供一组理论条件和一组操作指令,当满足每个条件时计算机就会采取这些操作。尽管使用条件语句以及数学和逻辑运算符进行流控制可以做很多事情,但是引入了函数和类之后,代码会变得更加高效,因为它们使你可以定义子程序。例如,如果应用程序非常频繁地需要一个确认对话框,那么将其作为类的实例编写一次要比每次需要它时重新编写实现起来要容易得多。

你需要学习如何在编程语言中定义类和函数。更准确地说,你首先需要了解编程语言中是否支持类和函数。大多数现代语言都支持函数,但是类是面向对象的编程语言中所特有的。

实践

学习语言中可用的结构,这些结构可以帮助你高效地编写和使用代码。

你可以学到任何东西

学习编程语言,就其本身而言,是一种编码过程中的子程序。一旦理解了代码如何工作,你所使用的语言就只是一种传递逻辑的媒介。学习一门新编程语言的过程几乎都是一样的:通过简单的练习来学习语法,通过学习词汇来积累进行复杂动作的能力,然后练习、练习、再练习。


via: https://opensource.com/article/20/10/learn-any-programming-language

作者:Seth Kenlon 选题:lujun9972 译者:xiao-song-123 校对:wxy

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

阅读本文并下载我们的免费备忘单,去使用开源的数据库吧。

当你写一个程序或配置一个服务时,你最终都要持久化存储信息。有时候,你只需要一个 INI 或者 YAML 配置文件就够了。而有时候,一个自定义格式的 XML 或者 JSON 或其他类似的文件会更好。

但也有时候你需要校验输入、快速查询信息、关联数据、通常还要熟练地处理你的用户的请求。这就是设计数据库的目的,而 MariaDB(由 MySQL 的原始开发人员开发的一个分支) 是一个极佳的选项。在本文中我使用的是 MariaDB,但这些信息同样适用于 MySQL。

通过编程语言与数据库进行交互是很普遍的。正因如此,出现了大量 Java、Python、Lua、PHP、Ruby、C++ 和其他语言的 SQL 库。然而,在使用这些库之前,理解数据库引擎做了什么以及为什么选择数据库是重要的对我们会很有帮助。本文介绍 MariaDB 和 mysql 命令来帮助你熟悉数据库处理数据的基本原理。

如果你还没有安装 MariaDB,请查阅我的文章 在 Linux 上安装 MariaDB。如果你没有使用 Linux,请参照 MariaDB 下载页面提供的指导方法。

与 MariaDB 交互

你可以使用 mysql 命令与 MariaDB 进行交互。首先使用子命令 ping 确认你的服务是运行着的,在提示后输入密码:

$ mysqladmin -u root -p ping
Enter password:
mysqld is alive

为了易于读者理解,打开一个交互式的 MariaDB 会话:

$ mysql -u root -p
Enter password:
Welcome to the MariaDB monitor.
Commands end with ; or \g.
[...]
Type 'help;' or '\h' for help.
Type '\c' to clear the current input statement.

MariaDB [(none)]>

你现在是在一个 MariaDB 子 shell 中,提示符是 MariaDB 提示符。普通的 Bash 命令在这里不能使用,只能用 MariaDB 命令。输入 help (或 ?)查看命令列表。这些是你的 MariaDB shell 的管理命令,使用它们可以定制你的 shell,但它们不属于 SQL 语言。

学习 SQL 基本知识

结构化查询语言是基于它们的能力定义的:一种通过有规则且一致的语法来查询数据库中的内容以得到有用的结果的方法。SQL 看起来像是普通的英文语句,有一点点生硬。例如,如果你登入数据库服务器,想查看有哪些库,输入 SHOW DATABASES; 并回车就能看到结果。

SQL 命令以分号作为结尾。如果你忘记输入分号,MariaDB 会认为你是想在下一行继续输入你的查询命令,在下一行你可以继续输入命令也可以输入分号结束命令。

MariaDB [(NONE)]> SHOW DATABASES;
+--------------------+
| DATABASE           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| test               |
+--------------------+
4 ROWS IN SET (0.000 sec)

上面的例子显示当前有四个数据库:information_schemamysqlperformance_schematest。你必须指定 MariaDB 使用哪个库,才能对该库使用查询语句。指定数据库的命令是 use。当你选择了一个库后,MariaDB 提示框会切换为选择的库。

MariaDB [(NONE)]> USE test;
MariaDB [(test)]>

显示数据库的表

数据库里有,与电子表格类似:有一系列的行(在数据库中称为记录)和列。一个行和一个列唯一确定一个字段

查看一个数据库中可用的表(可以理解为多表单电子表格中的一页),使用 SQL 关键字 SHOW

MariaDB [(test)]> SHOW TABLES;
empty SET

test 数据库是空的,所以使用 use 命令切换到 mysql 数据库:

MariaDB [(test)]> USE mysql;
MariaDB [(mysql)]> SHOW TABLES;

+---------------------------+
| Tables_in_mysql           |
+---------------------------+
| column_stats              |
| columns_priv              |
| db                        |
[...]
| time_zone_transition_type |
| transaction_registry      |
| USER                      |
+---------------------------+
31 ROWS IN SET (0.000 sec)

这个数据库中有很多表!mysql 数据库是这个 MariaDB 实例的系统管理数据库。它里面包含重要数据,比如用来管理数据库权限的用户结构。这个数据库很重要,你不需要经常直接与它交互,但是使用 SQL 脚本来操作它却很常见。当你学习 MariaDB 时理解 mysql 数据库很有用,因为它有助于说明一些基本的 SQL 命令。

检查一个表

这个实例的 mysql 数据库的最后一个表名为 USER。这个表包含了可以访问这个数据库的用户。当前里面只有一个 root 用户,但是你可以添加不同权限的用户,赋予它们查看、更新或创建数据的权限。你可以查看一个表的列首来了解一个 MariaDB 用户的所有属性:

> SHOW COLUMNS IN USER;
MariaDB [mysql]> SHOW COLUMNS IN USER;
+-------------+---------------+------+-----+----------+
| FIELD       | TYPE          | NULL | KEY | DEFAULT  |
+-------------+---------------+------+-----+----------+
| Host        | CHAR(60)      | NO   | PRI |          |
| USER        | CHAR(80)      | NO   | PRI |          |
| Password    | CHAR(41)      | NO   |     |          |
| Select_priv | enum('N','Y') | NO   |     | N        |
| Insert_priv | enum('N','Y') | NO   |     | N        |
| Update_priv | enum('N','Y') | NO   |     | N        |
| Delete_priv | enum('N','Y') | NO   |     | N        |
| Create_priv | enum('N','Y') | NO   |     | N        |
| Drop_priv   | enum('N','Y') | NO   |     | N        |
[...]
47 ROWS IN SET (0.001 sec)

创建一个新的用户

不论你是否需要一个普通的账号来管理数据库或者为计算机配置数据库(例如安装 WordPress、Drupal 或 Joomla时),在 MariaDB 中多建一个用户账号是很普遍的。你可以通过向 mysql 数据库的 USER 表中添加一个用户或使用 SQL 关键字 CREATE 来提示 MariaDB 创建一个 MariaDB 用户。使用 CREATE 来创建新用户会默认执行一些有用的方法,因此你不需要手动生成所有的信息:

> CREATE USER 'tux'@'localhost' IDENTIFIED BY 'really_secure_password';

查看表的字段

你可以使用 SELECT 关键字来查看数据库表的字段和值。这本例中,你创建了一个名为 tux 的用户,因此查询 USER 表中的列:

> SELECT USER,host FROM USER;
+------+------------+
| USER | host       |
+------+------------+
| root | localhost  |
[...]
| tux  | localhost  |
+------+------------+
7 ROWS IN SET (0.000 sec)

为一个用户赋予权限

通过查看 USER 表列出的信息,你可以看到用户的状态。例如,新用户 tux 对这个数据库没有任何权限。使用 WHERE 语句你可以只查 tux 那一条记录。

> SELECT USER,select_priv,insert_priv,update_priv FROM USER WHERE USER='tux';
+------+-------------+-------------+-------------+
| USER | select_priv | insert_priv | update_priv |
+------+-------------+-------------+-------------+
| tux  | N           | N           | N           |
+------+-------------+-------------+-------------+

使用 GRANT 命令修改用户的权限:

> GRANT SELECT ON *.* TO 'tux'@'localhost';
> FLUSH PRIVILEGES;

验证你的修改:

> SELECT USER,select_priv,insert_priv,update_priv FROM USER WHERE USER='tux';
+------+-------------+-------------+-------------+
| USER | select_priv | insert_priv | update_priv |
+------+-------------+-------------+-------------+
| tux  | Y           | N           | N           |
+------+-------------+-------------+-------------+

tux 用户现在有了从所有表中查询记录的权限。

创建自定义的数据库

到目前为止,你一直在与默认的数据库进行交互。除了用户管理,大部分人很少会与默认的数据库进行交互。通常,你会用自定义的数据来填充创建的数据库。

创建一个 MariaDB 数据库

你可能已经可以自己在 MariaDB 中创建新数据库了。创建数据库跟新建用户差不多。

> CREATE DATABASE example;
Query OK, 1 ROW affected (0.000 sec)
> SHOW DATABASES;
+--------------------+
| DATABASE           |
+--------------------+
| example            |
[...]

使用 use 命令来把这个新建的数据库作为当前使用的库:

> USE example;

创建一个表

创建表比创建数据库要复杂,因为你必须定义列首。MariaDB 提供了很多方便的函数,可以用于创建列,引入数据类型定义,自增选项,对空值的约束,自动时间戳等等。

下面是用来描述一系列用户的一个简单的表:

> CREATE TABLE IF NOT EXISTS member (
    -> id INT AUTO_INCREMENT PRIMARY KEY,
    -> name VARCHAR(128) NOT NULL,
    -> startdate TIMESTAMP DEFAULT CURRENT_TIMESTAMP);
Query OK, 0 ROWS affected (0.030 sec)

这个表通过使用一个自动递增的方法来唯一标识每一行。表示用户名字的字段不能为空(或 null),每一行被创建时会自动生成时间戳。

使用 SQL 关键字 INSERT 向这个表填充一些示例数据:

> INSERT INTO member (name) VALUES ('Alice');
Query OK, 1 ROW affected (0.011 sec)
> INSERT INTO member (name) VALUES ('Bob');
Query OK, 1 ROW affected (0.011 sec)
> INSERT INTO member (name) VALUES ('Carol');
Query OK, 1 ROW affected (0.011 sec)
> INSERT INTO member (name) VALUES ('David');
Query OK, 1 ROW affected (0.011 sec)

验证一下表里的数据:

> SELECT * FROM member;
+----+-------+---------------------+
| id | name  | startdate           |
+----+-------+---------------------+
|  1 | Alice | 2020-10-03 15:25:06 |
|  2 | Bob   | 2020-10-03 15:26:43 |
|  3 | Carol | 2020-10-03 15:26:46 |
|  4 | David | 2020-10-03 15:26:51 |
+----+-------+---------------------+
4 ROWS IN SET (0.000 sec)

同时增加多行数据

再创建一个表:

> CREATE TABLE IF NOT EXISTS linux (
    -> id INT AUTO_INCREMENT PRIMARY KEY,
    -> distro VARCHAR(128) NOT NULL);
Query OK, 0 ROWS affected (0.030 sec)

填充一些示例数据,这次使用 VALUES 快捷方式,这样你可以一次添加多行数据。VALUES 关键字需要一个用括号包围的列表作为参数,也可以用逗号分隔的多个列表作为参数。

> INSERT INTO linux (distro)
 -> VALUES ('Slackware'), ('RHEL'),('Fedora'),('Debian');
Query OK, 4 ROWS affected (0.011 sec)
Records: 4  Duplicates: 0  Warnings: 0
> SELECT * FROM linux;
+----+-----------+
| id | distro    |
+----+-----------+
|  1 | Slackware |
|  2 | RHEL      |
|  3 | Fedora    |
|  4 | Debian    |
+----+-----------+

关联多个表

现在你有两个表,之间没有关联。两个表的数据是独立的,但是你可能需要表一中的一个值来识别表二的记录。

你可以在表一中新增一列对应表二中的值。因为两个表都有唯一的标识符(自动递增的 id 字段),关联的它们的最简单的方式是,使用表一中的 id 字段作为表二的查询条件。

在表一中创建一列用来表示表二中的一个值:

> ALTER TABLE member ADD COLUMN (os INT);
Query OK, 0 ROWS affected (0.012 sec)
Records: 0  Duplicates: 0  Warnings: 0
> DESCRIBE member;
DESCRIBE member;
+-----------+--------------+------+-----+---------+------+
| FIELD     | TYPE         | NULL | KEY | DEFAULT | Extra|
+-----------+--------------+------+-----+---------+------+
| id        | INT(11)      | NO   | PRI | NULL    | auto_|
| name      | VARCHAR(128) | NO   |     | NULL    |      |
| startdate | TIMESTAMP    | NO   |     | cur[...]|      |
| os        | INT(11)      | YES  |     | NULL    |      |
+-----------+--------------+------+-----+---------+------+

linux 表中的唯一 ID 分配给每个成员。因为记录已经存在,使用 UPDATE 关键字而不是 INSERT。尤其是当你想查询某行然后再更新某列值时。语法上,表达方式有点倒装,先更新后查询:

> UPDATE member SET os=1 WHERE name='Alice';
Query OK, 1 ROW affected (0.007 sec)
ROWS matched: 1  Changed: 1  Warnings: 0

要填充数据,请对其他名字重复执行这个过程。为了数据的多样性,在四行记录中分配三个不同的值。

连接表

现在这两个表彼此有了关联,你可以使用 SQL 来展示关联的数据。数据库中有很多种连接方式,你可以尽请尝试。下面的例子是关联 member 表中 os 字段和 linux 表中 id 字段:

SELECT * FROM member JOIN linux ON member.os=linux.id;
+----+-------+---------------------+------+----+-----------+
| id | name  | startdate           | os   | id | distro    |
+----+-------+---------------------+------+----+-----------+
|  1 | Alice | 2020-10-03 15:25:06 |    1 |  1 | Slackware |
|  2 | Bob   | 2020-10-03 15:26:43 |    3 |  3 | Fedora    |
|  4 | David | 2020-10-03 15:26:51 |    3 |  3 | Fedora    |
|  3 | Carol | 2020-10-03 15:26:46 |    4 |  4 | Debian    |
+----+-------+---------------------+------+----+-----------+
4 ROWS IN SET (0.000 sec)

连接 osid 字段。

在图形化的应用中,你可以想象 os 字段可以在下拉菜单中设置,值的来源是 linux 表中的 distro 字段。通过使用多个表中独立却有关联的数据,你可以保证数据的一致性和有效性,使用 SQL 你可以动态地关联它们。

下载 MariaDB 和 MySQL 备忘单

MariaDB 是企业级的数据库。它是健壮、强大、高效的数据库引擎。学习它是你向管理 web 应用和编写语言库迈出的伟大的一步。你可以下载 MariaDB 和 MySQL 备忘单,在你使用 MariaDB 时可以快速参考。


via: https://opensource.com/article/20/10/mariadb-mysql-cheat-sheet

作者:Seth Kenlon 选题:lujun9972 译者:lxbwolf 校对:wxy

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

VizTracer 工具可以可视化并跟踪 Python 代码,让你可以更深入地了解其工作原理。

随着 Python 项目变得越来越大、越复杂,理解起它来就变得充满挑战性。即使是你自己独自编写了整个项目,也不可能完全知道项目是如何工作的。为了能更好的理解你的代码,调试和分析代码变得至关重要。

VizTracer 是一个这样的工具,它通过跟踪和可视化 Python 代码的执行过程,来帮助你对代码的理解。无需对源代码进行任何更改,VizTracer 即可记录函数的入口 / 出口,函数参数 / 返回值以及任意变量,然后通过 Trace-Viewer 使用直观的谷歌前端界面来显示数据。

下面是一个运行蒙特卡洛树搜索的例子:

 title=

每个函数都在时间线上以堆栈的形式记录和可视化,这样你就可以看到在运行程序时发生了什么。你可以放大查看任意特定点的详细信息:

 title=

VizTracer 还可以自动记录函数参数和返回值。你可以单击函数条目并查看详细信息:

 title=

或者你可以创建一个全新的信号,并用它来记录变量。例如,这显示了执行梯度下降时的成本值:

 title=

与其他设置复杂的工具相比,VizTracer 使用起来非常简单,并且没有任何依赖关系。你可以从 pip 安装它:

pip install viztracer

你也可以通过输入来跟踪你的程序(<your_script.py> 是你脚本的名称):

viztracer <your_script.py>

VizTracer 将在你的工作目录中生成一个 HTML 报告,你可以在 Chrome 浏览器中打开它。

VizTracer 还提供了其他高级功能,比如过滤器功能,你可以使用它过滤掉不想跟踪的函数,获得更清晰的报告。例如,要仅包含文件中的函数,你需要:

viztracer include_files ./ --run <your_script.py>

记录函数参数和返回值:

viztracer --log_function_args --log_return_value <your_script.py>

记录与某个正则表达式匹配的任意变量:

# log variables starts with a
viztracer --log_var a.* --run &lt;your_script.py&gt;

你可以通过对源代码进行较小的修改来获得其他功能,例如自定义事件来记录数值和对象。

VizTracer 还包括一个虚拟调试器(vdb),它可以调试 VizTracer 的日志文件。可以用 vdb 调试你运行中的代码(与 pdb 非常相似)以便你了解代码流。有用的是,它还支持时间回溯,因为它知道发生的一切。

与一些原型不同,VizTracer 使用纯 C 语言实现其核心,这将极大地减少开销,使其达到类似于 cProfile 的水平。

VizTracer 是开源的,在 Apache 2.0 许可下发布,支持所有常见的操作系统平台(Linux、macOS 和 Windows)。你可以在 GitHub 上了解关于它的更多特性并访问源代码。


via: https://opensource.com/article/20/11/python-code-viztracer

作者:Tian Gao 选题:lujun9972 译者:xiao-song-123 校对:wxy

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

可以使用相同的机器用于工作和个人的 Git 提交,而无需手动重置你的配置。

Git 的 git config 命令可以让你为 Git 设置仓库或全局选项。它有很多选项,其中的一个选项 includeIf 在你使用在 Git 时有双重角色时非常方便,比如说,你既是全职的开发者,又在业余时间为开源项目做贡献。在这种情况下,大多数人都不想为两个角色使用一个共同的配置,或者,至少,他们肯定希望保持配置的某些部分是不同的,尤其是当他们在两个角色中使用同一台计算机时。

我的情况就是这样,所以我在 Git 配置中保留了两组不同的邮件 ID。这样一来,在我工作场所的项目仓库中提交的内容就会使用我办公室的邮件 ID,而在我个人 GitHub 帐户中提交的内容则使用我个人的邮件 ID。

以下是我的全局配置(维护在 $HOME/.gitconfig)中的一个片段,我将在下文中介绍。

[includeIf "gitdir:~/priv_scm/"]
        path = ~/priv_scm/.gitconfig
[includeIf "gitdir:~/work_scm/"]
        path = ~/work_scm/.gitconfig

什么是 includeIf?

includeIf.condition.path 变量,是 include 配置指令的一部分,允许你有条件地设置自定义配置。同时,自定义配置的路径也可以作为指令的一部分来设置。

这个指令支持三个关键字:gitdirgitdir/Ionbranch。我将简单解释一下 gitdir,我在上面的代码片段中使用了它。你可以在文档中了解其他两个关键词。

includeIf 指令中使用 gitdir 关键字会对模式进行条件检查。根据规则,如果当前工作目录与 gitdir 中指定的目录模式相匹配,那么它就会从给定的路径中选取配置。我将在配置片段上应用这个规则来展示它是如何被应用的。

在配置片段中,你可以看到一个简单的模式,~/,它与 gitdir 关键字一起使用。这个模式会被存储在 $HOME 环境变量中的值所替代。

如何使用它

如果你使用同一个系统在 GitHub 或 GitLab 上的开源项目上工作,并在工作中提交到 Git 仓库,你可以有两个顶级目录,比如 $HOME/priv_scm$HOME/work_scm。在这两个目录中,你可以有两个单独的 .gitconfig 文件,其中包含与你的 user.nameuser.email 相关的设置。然而,它们也可以包含存储在 $HOME 的全局 .gitconfig 中,它可以保存两个环境通用的所有自定义项。

这里是一个例子 $HOME/priv_scm/.gitconfig 的片段:

$ cat $HOME/priv_scm/.gitconfig
[user]
    name  = Ramanathan Muthiah
    email = &lt;personal-mailid-goes-here&gt;

有了这个配置,你就可以切换目录,并开始在开源项目上工作,而无需手动重置一些与 Git 相关的配置。这些更改会在主 .gitconfig 中借助 includeIf 条件指令自动处理。

Git 小贴士

希望这个小贴士能帮助你组织 Git 项目。你最喜欢的 Git 小贴士是什么?请在评论中分享吧!


via: https://opensource.com/article/20/10/git-config

作者:Ramanathan M 选题:lujun9972 译者:geekpi 校对:wxy

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

在本系列的 第一第二 部分中讨论的大多数示例都是以某种方式闪烁的 LED。起初它可能很有趣,但是一段时间后变得有些无聊。让我们做些更有趣的事情……

…让我们点亮更多的 LED!

STM32F030F4P6

WS281x LED

WS281x RGB LED(及其克隆品)非常受欢迎。你可以以单个元素购买、链成长条或组装成矩阵、环或其他形状。

WS2812B

它们可以串联连接,基于这个事实,你可以只用 MCU 的单个引脚就可以控制一个很长的 LED 灯条。不幸的是,它们的内部控制器使用的物理协议不能直接适用于你在 MCU 中可以找到的任何外围设备。你必须使用 位脉冲 bit-banging 或以特殊方式使用可用的外设。

哪种可用的解决方案最有效取决于同时控制的 LED 灯条数量。如果你必须驱动 4 到 16 个灯条,那么最有效的方法是 使用定时器和 DMA(请不要忽略这篇文章末尾的链接)。

如果只需要控制一个或两个灯条,请使用可用的 SPI 或 UART 外设。对于 SPI,你只能在发送的一个字节中编码两个 WS281x 位。由于巧妙地使用了起始位和停止位,UART 允许更密集的编码:每发送一个字节 3 位。

我在 此站点 上找到了有关 UART 协议如何适用于 WS281x 协议的最佳解释。如果你不懂波兰语,这里是 英文翻译

基于 WS281x 的 LED 仍然是最受欢迎的,但市场上也有 SPI 控制的 LED:APA102SK9822。关于它们的三篇有趣的文章在这里:123

LED 环

市场上有许多基于 WS2812 的环。我有一个这样的:

WS2812B

它具有 24 个可单独寻址的 RGB LED(WS2812B),并暴露出四个端子:GND、5V、DI 和 DO。通过将 DI(数据输入)端子连接到上一个的 DO(数据输出)端子,可以链接更多的环或其他基于 WS2812 的东西。

让我们将这个环连接到我们的 STM32F030 板上。我们将使用基于 UART 的驱动程序,因此 DI 应连接到 UART 接头连接器上的 TXD 引脚。 WS2812B LED 需要至少 3.5V 的电源。 24 个 LED 会消耗大量电流,因此在编程/调试期间,最好将环上的 GND 和 5V 端子直接连接到 ST-LINK 编程器上可用的 GND 和 5V 引脚:

WS2812B

我们的 STM32F030F4P6 MCU 和整个 STM32 F0、F3、F7、L4 系列具有 F1、F4、L1 MCU 不具备的一项重要功能:它可以反转 UART 信号,因此我们可以将环直接连接到 UART TXD 引脚。如果你不知道我们需要这种反转,那么你可能没有读过我上面提到的 文章

因此,你不能以这种方式使用流行的 Blue PillSTM32F4-DISCOVERY。使用其 SPI 外设或外部反相器。有关使用 SPI 的 NUCLEO-F411RE,请参见 圣诞树灯 项目作为 UART + 逆变器的示例或 WS2812示例

顺便说一下,大多数 DISCOVERY 板可能还有一个问题:它们在 VDD = 3V 而不是 3.3V 的情况下工作。 对于高 DI,WS281x 至少要求电源电压 * 0.7。如果是 5V 电源,则为 3.5V;如果是 4.7V 电源,则为 3.3V;可在 DISCOVERY 的 5V 引脚上找到。如你所见,即使在我们的情况下,第一个 LED 的工作电压也低于规格 0.2V。对于 DISCOVERY 板,如果供电 4.7V,它将工作在低于规格的 0.3V 下;如果供电 5V,它将工作在低于规格 0.5V 下。

让我们结束这段冗长的介绍并转到代码:

package main

import (
    "delay"
    "math/rand"
    "rtos"

    "led"
    "led/ws281x/wsuart"

    "stm32/hal/dma"
    "stm32/hal/gpio"
    "stm32/hal/irq"
    "stm32/hal/system"
    "stm32/hal/system/timer/systick"
    "stm32/hal/usart"
)

var tts *usart.Driver

func init() {
    system.SetupPLL(8, 1, 48/8)
    systick.Setup(2e6)

    gpio.A.EnableClock(true)
    tx := gpio.A.Pin(9)

    tx.Setup(&gpio.Config{Mode: gpio.Alt})
    tx.SetAltFunc(gpio.USART1_AF1)

    d := dma.DMA1
    d.EnableClock(true)

    tts = usart.NewDriver(usart.USART1, d.Channel(2, 0), nil, nil)
    tts.Periph().EnableClock(true)
    tts.Periph().SetBaudRate(3000000000 / 1390)
    tts.Periph().SetConf2(usart.TxInv)
    tts.Periph().Enable()
    tts.EnableTx()

    rtos.IRQ(irq.USART1).Enable()
    rtos.IRQ(irq.DMA1_Channel2_3).Enable()
}

func main() {
    var rnd rand.XorShift64
    rnd.Seed(1)
    rgb := wsuart.GRB
    strip := wsuart.Make(24)
    black := rgb.Pixel(0)
    for {
        c := led.Color(rnd.Uint32()).Scale(127)
        pixel := rgb.Pixel(c)
        for i := range strip {
            strip[i] = pixel
            tts.Write(strip.Bytes())
            delay.Millisec(40)
        }
        for i := range strip {
            strip[i] = black
            tts.Write(strip.Bytes())
            delay.Millisec(20)
        }
    }
}

func ttsISR() {
    tts.ISR()
}

func ttsDMAISR() {
    tts.TxDMAISR()
}

//c:__attribute__((section(".ISRs")))
var ISRs = [...]func(){
    irq.USART1:          ttsISR,
    irq.DMA1_Channel2_3: ttsDMAISR,
}

导入部分

与前面的示例相比,导入部分中的新内容是 rand/math 包和带有 led/ws281x 子树的 led 包。 led 包本身包含 Color 类型的定义。 led/ws281x/wsuart 定义了 ColorOrderPixelStrip 类型。

我想知道如何使用 image/color 中的 ColorRGBA 类型,以及如何以它将实现 image.Image 接口的方式定义 Strip。 但是由于使用了 gamma 校正 和 大开销的 color/draw 包,我以简单的方式结束:

type Color uint32
type Strip []Pixel

使用一些有用的方法。然而,这种情况在未来可能会改变。

init 函数

init 函数没有太多新颖之处。 UART 波特率从 115200 更改为 3000000000/1390 ≈ 2158273,相当于每个 WS2812 位 1390 纳秒。 CR2 寄存器中的 TxInv 位设置为反转 TXD 信号。

main 函数

XorShift64 伪随机数生成器用于生成随机颜色。 XORSHIFT 是目前由 math/rand 包实现的唯一算法。你必须使用带有非零参数的 Seed 方法显式初始化它。

rgb 变量的类型为 wsuart.ColorOrder,并设置为 WS2812 使用的 GRB 颜色顺序(WS2811 使用 RGB 顺序)。然后用于将颜色转换为像素。

wsuart.Make(24) 创建 24 像素的初始化条带。它等效于:

strip := make(wsuart.Strip, 24)
strip.Clear()

其余代码使用随机颜色绘制类似于 “Please Wait…” 微调器的内容。

strip 切片充当帧缓冲区。 tts.Write(strip.Bytes()) 将帧缓冲区的内容发送到环。

中断

该程序由处理中断的代码组成,与先前的 UART 示例 中的代码相同。

让我们编译并运行:

$ egc
$ arm-none-eabi-size cortexm0.elf
   text    data     bss     dec     hex filename
  14088     240     204   14532    38c4 cortexm0.elf
$ openocd -d0 -f interface/stlink.cfg -f target/stm32f0x.cfg -c 'init; program cortexm0.elf; reset run; exit'

我跳过了 openocd 的输出。下面的视频显示了该程序的工作原理:

让我们做些有用的事情...

第一部分 的开头,我曾问过:“Go 能深入到多低层,而还能做一些有用的事情?”。 我们的 MCU 实际上是一种低端设备(8 比特的人可能会不同意我的看法),但到目前为止,我们还没有做任何有用的事情。

所以... 让我们做些有用的事情... 让我们做个时钟!

在互联网上有许多由 RGB LED 构成的时钟示例。让我们用我们的小板子和 RGB 环制作自己的时钟。我们按照下面的描述更改先前的代码。

导入部分

删除 math/rand 包,然后添加 stm32/hal/exti

全局变量

添加两个新的全局变量:btnbtnev

var (
    tts   *usart.Driver
    btn   gpio.Pin
    btnev rtos.EventFlag
)

它们将用来处理那些用于设置时钟的 “按钮”。我们的板子除了重置之外没有其他按钮,但是如果没有它,我们仍然可以通过某种方式进行管理。

init 函数

将这段代码添加到 init 函数:

btn = gpio.A.Pin(4)

btn.Setup(&gpio.Config{Mode: gpio.In, Pull: gpio.PullUp})
ei := exti.Lines(btn.Mask())
ei.Connect(btn.Port())
ei.EnableFallTrig()
ei.EnableRiseTrig()
ei.EnableIRQ()

rtos.IRQ(irq.EXTI4_15).Enable()

在内部 上拉电阻 pull-up resistor 启用的情况下,将 PA4 引脚配置为输入。它已连接至板载 LED,但这不会妨碍任何事情。更重要的是它位于 GND 引脚旁边,所以我们可以使用任何金属物体来模拟按钮并设置时钟。作为奖励,我们还有来自板载 LED 的其他反馈。

我们使用 EXTI 外设来跟踪 PA4 状态。它被配置为在发生任何更改时都会产生中断。

btnWait 函数

定义一个新的辅助函数:

func btnWait(state int, deadline int64) bool {
    for btn.Load() != state {
        if !btnev.Wait(1, deadline) {
            return false // timeout
        }
        btnev.Reset(0)
    }
    delay.Millisec(50) // debouncing
    return true
}

它等待 “按钮” 引脚上的指定状态,但只等到最后期限出现。这是稍微改进的轮询代码:

for btn.Load() != state {
    if rtos.Nanosec() >= deadline {
        // timeout
    }
}

我们的 btnWait 函数不是忙于等待 statedeadline,而是使用 rtos.EventFlag 类型的 btnev 变量休眠,直到有事情发生。你当然可以使用通道而不是 rtos.EventFlag,但是后者便宜得多。

main 函数

我们需要全新的 main 函数:

func main() {
    rgb := wsuart.GRB
    strip := wsuart.Make(24)
    ds := 4 * 60 / len(strip) // Interval between LEDs (quarter-seconds).
    adjust := 0
    adjspeed := ds
    for {
        qs := int(rtos.Nanosec() / 25e7) // Quarter-seconds since reset.
        qa := qs + adjust

        qa %= 12 * 3600 * 4 // Quarter-seconds since 0:00 or 12:00.
        hi := len(strip) * qa / (12 * 3600 * 4)

        qa %= 3600 * 4 // Quarter-seconds in the current hour.
        mi := len(strip) * qa / (3600 * 4)

        qa %= 60 * 4 // Quarter-seconds in the current minute.
        si := len(strip) * qa / (60 * 4)

        hc := led.Color(0x550000)
        mc := led.Color(0x005500)
        sc := led.Color(0x000055)

        // Blend the colors if the hands of the clock overlap.
        if hi == mi {
            hc |= mc
            mc = hc
        }
        if mi == si {
            mc |= sc
            sc = mc
        }
        if si == hi {
            sc |= hc
            hc = sc
        }

        // Draw the clock and write to the ring.
        strip.Clear()
        strip[hi] = rgb.Pixel(hc)
        strip[mi] = rgb.Pixel(mc)
        strip[si] = rgb.Pixel(sc)
        tts.Write(strip.Bytes())

        // Sleep until the button pressed or the second hand should be moved.
        if btnWait(0, int64(qs+ds)*25e7) {
            adjust += adjspeed
            // Sleep until the button is released or timeout.
            if !btnWait(1, rtos.Nanosec()+100e6) {
                if adjspeed < 5*60*4 {
                    adjspeed += 2 * ds
                }
                continue
            }
            adjspeed = ds
        }
    }
}

我们使用 rtos.Nanosec 函数代替 time.Now 来获取当前时间。这样可以节省大量的闪存,但也使我们的时钟变成了不知道日、月、年的老式设备,最糟糕的是它无法处理夏令时的变化。

我们的环有 24 个 LED,因此秒针的显示精度可以达到 2.5 秒。为了不牺牲这种精度并获得流畅的运行效果,我们使用 1/4 秒作为基准间隔。半秒就足够了,但四分之一秒更准确,而且与 16 和 48 个 LED 配合使用也很好。

红色、绿色和蓝色分别用于时针、分针和秒针。这允许我们使用简单的“逻辑或操作”进行颜色混合。我们 Color.Blend 方法可以混合任意颜色,但是我们闪存不多,所以我们选择最简单的解决方案。

我们只有在秒针移动时才重画时钟。

btnWait(0, int64(qs+ds)*25e7)

上面的这行代码等待的正是那一刻,或者是按钮的按下。

每按一下按钮就会把时钟向前调一调。按住按钮一段时间会加速调整。

中断

定义新的中断处理程序:

func exti4_15ISR() {
    pending := exti.Pending() & 0xFFF0
    pending.ClearPending()
    if pending&exti.Lines(btn.Mask()) != 0 {
        btnev.Signal(1)
    }
}

并将 irq.EXTI4_15: exti4_15ISR 条目添加到 ISR 数组。

该处理程序(或中断服务程序)处理 EXTI4\_15 IRQ。 Cortex-M0 CPU 支持的 IRQ 明显少于其较大的同类兄弟处理器,因此你经常可以看到一个 IRQ 被多个中断源共享。在我们的例子中,一个 IRQ 由 12 个 EXTI 线共享。

exti4\_15ISR 读取所有挂起的位,并从中选择 12 个更高的有效位。接下来,它清除 EXTI 中选中的位并开始处理它们。在我们的例子中,仅检查第 4 位。 btnev.Signal(1) 引发 btnev.Wait(1, deadline) 唤醒并返回 true

你可以在 Github 上找到完整的代码。让我们来编译它:

$ egc
$ arm-none-eabi-size cortexm0.elf
   text    data     bss     dec     hex filename
  15960     240     216   16416    4020 cortexm0.elf

这里所有的改进只得到 184 个字节。让我们再次重新构建所有内容,但这次在 typeinfo 中不使用任何类型和字段名:

$ cd $HOME/emgo
$ ./clean.sh
$ cd $HOME/firstemgo
$ egc -nf -nt
$ arm-none-eabi-size cortexm0.elf
   text    data     bss     dec     hex filename
  15120     240     216   15576    3cd8 cortexm0.elf

现在,有了千字节的空闲空间,你可以改进一些东西。让我们看看它是如何工作的:

我不知道我是怎么精确打到 3:00 的!?

以上就是所有内容!在第 4 部分(本系列的结束)中,我们将尝试在 LCD 上显示一些内容。(LCTT 译注:然而烂尾了,第三篇写于 2018 年,整个博客当年就停更了。)


via: https://ziutek.github.io/2018/05/03/go_on_very_small_hardware3.html

作者:Michał Derkacz 选题:lujun9972 译者:gxlct008 校对:wxy

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