Diviner:确定性测试的新尝试

2020-06-30 11:11 栏目:经验之谈 来源:网络整理 查看()

我一直对确定性执行感兴趣。我们在多线程模型上花了很多时间。我们大多数人都应该遇到一些只在一定概率范围内发生的错误。即使你已经准备了一个修复程序,你也不能确定它是否会再次发生。你所能做的就是测试,测试,再测试,希望这个问题不会再发生。

我们可以肯定地调试并拍着胸脯说这是100%确定的,这是每个工程师的梦想,这个问题已经解决了。

在过去的几个月里,我一直在研究TLA,现在我坚信TLA是构建复杂的、多线程的、高性能的(可能是分布式的)系统的宝贵工具。在为我所有的项目写第一行代码之前,我真的更喜欢在TLA进行设计。然而,TLA只能帮助你思考你的设计和修复设计缺陷。我们还需要考虑另一个方面:系统的实际实现。

我们可以有一个已经被TLA验证过的设计,但是如果你写的代码容易受到一些并发错误的攻击,并且这些错误以一定的概率发生,我们现在该怎么办?

当然,也有一些解决方案的尝试,比如rr。但是在这个领域还有另一个真正的本质,那就是基础数据库。如果您不太了解基础数据库,尤其是它们是如何测试的,我强烈推荐以下两个视频:

https://www.youtube.com/watch?v=4fFDFbi3toc

https://www.youtube.com/watch?v=fFSPwJFXVlw

他们所做的是在C之上构建一个参与者模型,然后使用这个参与者模型来编写完整的数据库逻辑。因此,他们可以将基于参与者的代码完全注入到确定性测试框架中,以测试各种并发问题。

老实说,我以前看过这些视频,但当时我对它们的解决方案印象不是很深。原因是他们的模拟框架在一个线程中连续运行,这与真实环境相去甚远。在真实环境中,您有多个线程一起运行。所以你从模拟中得到的性能数据是没有意义的。

幸运的是,我有一个爱好,那就是成为一名计算机科学的考古学家:我会时不时地翻出一些相对旧的视频,然后再看一遍,以获得新的理解。这是过去的经验,在这个行业有许多新发明,它们只是新瓶装旧酒。最近,当我翻出了创建数据库的视频并再次观看时,我发现我以前犯了一个非常非常严重的错误。这真的是一件大事。

测试不同于基准测试

这里的关键是测试不同于基准测试。测试的目的不是获得实际运行时间,而是探索程序可以采用的所有途径。正如TLA将探索您设计中的所有状态一样,如果模拟能够探索一段代码可以采用的所有执行路径,这就足够了。使用由参与者模型重组的代码,您的逻辑自然会被分成许多小的原子块。只要你能列举出一个程序能有效执行的所有不同的执行序列,即使是单线程测试框架,你也能探索出多线程解决方案中可能出现的所有路径!

事实上,在模拟环境中有很多优势:当你的项目发布时,人们可能会开始使用它,这意味着他们会尝试在许多不同的机器上运行你的代码。然后,这些机器将探索您的程序可能导致的不同执行状态。在某种程度上,我们可以认为所有这些机器都在测试你的程序并寻找漏洞。为了确保项目的质量,在理想的情况下,你应该在所有这些不同的机器之前发现新的错误。现在,问题变成了一场列出所有可能的状态并找出错误的竞赛。对于一些流行的产品,用户运行的机器比项目维护者拥有的机器多是很容易的。问题来了:我们如何用很少的机器找到更多的bug?

这个问题的答案类似于FoundationDB解决方案中的模拟设计:首先,我们在一个参与者模型的框架下组织逻辑,这样我们就可以使用一个单线程模拟测试执行器来运行测试;然后,我们模拟所有与环境相关的代码,如定时器、网络操作系统、文件操作系统等等。通过这种方式,我们可以将项目的核心,也就是大多数bug发生的地方,细化成单线程的、连续的代码,具有以下优点:

当测试在单线程环境中运行时,典型的多核机器可以用来同时运行多个测试;

当所有的操作系统都被模拟时,我们可以在测试中运行更少的代码(例如,我们可以跳过整个TCP/IP堆栈),以便更快地进行测试;

通过模拟IOs,我们可以更容易地模拟异常情况,如网络拥塞;

所有这些优点意味着模拟方案可以让我们在更短的时间内测试更多的代码,并且给我们一个在寻找bug的赛车游戏中获胜的机会。在创建数据库的例子中,他们估计在过去几年中,通过这种设计,他们已经积累了相当于1万亿个CPU小时的模拟压力测试。直到今天,我还没有看到更高级的测试框架设计。

现在只有一个问题:尽管这个解决方案很好,而且被证明非常有效,但我们能在其他地方使用它吗?我们会被C演员的框架所限制吗?答案是,当然不是!

铁锈:基于演员模拟测试的最佳选择

如果我们仔细考虑一下,我们需要做的就是对基础数据库类型进行确定性的模拟测试,这是一个基于参与者的代码,然后我们可以根据测试需求对它们进行重组。令人兴奋的是,我们钟爱的构建高性能分布式软件的解决方案Rust提供了一个异步/等待设计,它非常类似于演员模型(好吧,我不是计算机科学教授,我把这个问题留给那些更有资格判断异步/等待是否是演员模型的人)。为了让它更有趣,Rust的可切换运行时的设计使它成为确定性模拟测试思想下的最佳选择:我们需要做的是在测试中使用不同的运行时,这个问题将得到解决。

接下来,让我们邀请这篇文章的主角:piner

https://github.com/xxuejie/piner

piner

一旦我有了这个想法,我觉得它真的很棒。毫不夸张地说,我花了所有的夜晚和周末来实现这个想法,然后我变得更加憔悴。它由两部分组成:

一个被设计成单线程和确定性的运行时间,所以我们可以用它来构建确定性的模拟测试;

封装器在现有的Rust异步库上实现。在正常模式下(通过内联函数和新类型),包装器将被直接编译到现有的实现中,但是在启用特殊的模拟函数之后,它们将被编译到与上述运行时集成的模拟版本中,用于确定性测试。现在我从异步标准开始,但是将来可能会添加更多的包装器。

结合这两个部分,piner为异步/等待的Rust代码提供了一个确定性的测试解决方案。这里有一些例子来展示操作时间的能力,这允许我们以更快的方式测试超时,并测试并发错误的能力。有了确定性种子,piner将确定性地运行,给你无限期调试代码的机会。它的美妙之处在于它只是一个异步/等待的生锈代码,并且我们没有在piner中引入任何新的东西。

我还有一个例子,我希望在未来几天内能够实现:

使用byteorder:{ByteOrder,little endian };

使用piner:{

net:{TcpListener,TcpStream},

产卵,环境,

};

使用std:io

异步fn句柄(stream: Tcpstream) {

让mut buf=vec![];

循环{

让mut t=vec![0;1024];

让n=stream . read(mut t)wait . expect(' read error!');

如果n==0 {

休息;

}

buf.extend_from_slice(t[.n]);

设l=little endian : read _ u32(buf)为usize

if buf.len()=l 4 {

让内容=buf[4.l 4];

stream.write(内容),wait.expect('写错误!');

buf=buf .漏极(0.l 4)。收集();

}

}

}

异步fn服务器(addr:字符串)-结果(),io:Error错误{

让mut侦听器=tcplistener : Bind(addr)。等待?

让Ok((stream,_))=listener.accept()。等待{

产卵(处理(流));

}

好(())

}

fn main() {

让e=环境:新();

让结果=e.block_on(异步{

让addr=' 127 . 0 . 0 . 1:18000 ';

产卵(异步{

服务器(addr.to_string())。);

});

让data: Vecu8=vec![4,0,0,0,0x64,0x61,0x64,0x 61];

对我来说.data.len() {

让mut client=TCPstream : connect(addr)wait . expect('连接错误!');

客户。写入数据[.I))。等待。预期('客户端写入1错误!');

客户。写入(数据[i.])。等待。预期('客户端写入1错误!');

让mut输出: Vecu8=vec![0;4];

client.read(mut输出),wait.expect('client read error!');

if输出[.]!=数据[4.] {

恐慌。(“无效响应!”);

}

}

});

匹配结果{

Ok(val)=println!(“任务完成于{:} ',val),

Err(err)=println!(“任务已陷入恐慌?}’,呃),

}

}

这个例子显示了一个典型的新手错误:TCP/IP协议是基于流量的,而不是基于数据包的。虽然您可能能够提供一个1 KB的缓冲区,但是协议可以用任意数量的字节来反馈您,并且在极端情况下可能只有一个字节的数据。在实际测试中,很难模拟,因为您需要创建一个非常拥挤的TCP/IP环境,只有一个非常小且非常拥挤的窗口。但是对于piner来说,在测试中调整它会非常简单。您编写的代码只使用TcpListener/TCPStream,就像异步标准中同名的结构一样。是的,您必须通过piner导入它们,但是使用内联函数和新类型模式,性能将不会受到任何影响。一旦你愿意做出这样的牺牲,我相信你会找到一个全新的世界。

这就是让我兴奋的地方。现在,piner仍然处于非常早期的阶段,当我有时间的时候,我会继续在piner中添加丢失的部分(比如异步标准中丢失的包装器)。如果你感兴趣,请过来试试,让我知道你的感受。

微信二维码
售前客服二维码

文章均源于网络收集编辑侵删

提示:仅接受技术开发咨询!

郑重申明:资讯文章为网络收集整理,官方公告以外的资讯内容与本站无关!
NFT开发,NFT交易所开发,DAPP开发 Keywords: NFT开发 NFT交易所开发 DAPP开发