<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Tony Bai &#187; LCUT</title>
	<atom:link href="http://tonybai.com/tag/lcut/feed/" rel="self" type="application/rss+xml" />
	<link>https://tonybai.com</link>
	<description>一个程序员的心路历程</description>
	<lastBuildDate>Mon, 08 Jun 2026 23:32:23 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>只为那一抹释然</title>
		<link>https://tonybai.com/2013/12/26/just-for-being-relieved/</link>
		<comments>https://tonybai.com/2013/12/26/just-for-being-relieved/#comments</comments>
		<pubDate>Wed, 25 Dec 2013 23:18:30 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[Coding-Review]]></category>
		<category><![CDATA[Commit-Log]]></category>
		<category><![CDATA[Jenkins]]></category>
		<category><![CDATA[KM]]></category>
		<category><![CDATA[LCUT]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[Refactoring]]></category>
		<category><![CDATA[ReviewBoard]]></category>
		<category><![CDATA[Subversion]]></category>
		<category><![CDATA[代码评审]]></category>
		<category><![CDATA[单元测试]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[安装包]]></category>
		<category><![CDATA[工作]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[感悟]]></category>
		<category><![CDATA[持续集成]]></category>
		<category><![CDATA[生活]]></category>
		<category><![CDATA[知识库]]></category>
		<category><![CDATA[知识管理]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[遗留系统]]></category>
		<category><![CDATA[重构]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1461</guid>
		<description><![CDATA[一切没有目标的努力，都是瞎忙活儿。 &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; - Tony Bai 刚实施回来，就又投入到新工作中，到今天才有那么一点点时间写写这件事儿。 * 缘起 我们的遗留系统性能一直不高，导致这一局面的因素有很多，比如最初设计和实现的&#8220;考虑不足&#8221;、后续维护人员的&#8220;随波逐流&#8221;甚至缺少勇气对影响性能的关 键代码进行重构等等。技术债务就这样一直积累着。直到两年前，我们终见其导致的巨大的影响了。 由于客户方成本压缩，单节点性能低意味着需要更多的硬件投入，并连带着报价升高，导致我们的产品市场竞争力下降。而竞争对手产品的性能是我们的 3-5倍，这终于引起了领导的重视，并下达了开发高性能版本的任务命令。 * 抉择 遗留系统的问题有很多，性能差仅仅是表象之一。可维护性差更让人印象深刻。遗留系统就像一件打满补丁的旧衣裳，虽然依旧能穿着遮体御寒，但却让我 们时刻战战兢兢，生怕一个动作会导致它解体，变得支离破碎。 对于我们这样一个mission-critical的系统来说，开发周期显然是不会短的。在性能达标的同时，更为重要的是保证产品的质量，确保上 线后运行稳定。因此摆在我们面前有两条路： &#160;&#160;&#160; 1、在遗留系统上做&#8220;大修&#8221; &#8211; 大规模重构； &#160;&#160;&#160; 2、重写，把构成系统的骨架重新设计和实现，使它能够足够坚固，满足在&#8220;高速公路&#8221;上驰骋的要求。 我们最终选择了重写，也就是风险较大的那条路。在我们的理解中，重写软件就好比汽车升级平台，就像大众将传统的PQ25、PQ35等统统升级为 MQB平台那样。平台的升级，不光影响技术，还会影响方方面面，比如团队的能力、思维方式、合作模式以及团队过程改善等等。做 得好的话，会使整个团队迈上一个新台阶，这是原地修补所不能够带来的。 对于我个人来说，这也是我期望中的实验田，我将把之前研究的诸多实践落地，帮助团队提升能力。 自私地说，重写系统也是我的一个小理想，能遇到这样一个从无到有构建一个系统的机会是不多的，因此很是希望能看到一个系统一点一点的在自己的呵护 下&#8220;成长&#8221;起来。虽然我也清楚完成这样一个系统需要很长时间，而这期间我可能需要时刻紧绷着神经，直到系统正式上线后，才能感受到那一抹释然。 * 建立&#8220;骨架(skeleton)&#8221; 我们将项目分成两个阶段：建立系统&#8220;骨架&#8221;和为系统&#8220;添肉&#8221;，即添加业务逻辑。 系统的性能目标是原遗留系统的10倍，这样我们建立的骨架的性能至少要高于原遗留系统的10倍。在&#8220;添肉&#8221;之前我们要充分证明骨架的设计是合理、 有效、稳定和高性能的。 遗留系统性能低，并非因为当初的设计者能力有什么问题，更多是局限于当初的设计目标。系统初期业务量不大，接入的外部网元不多，因此系统大量使用 了链表这种简单但低效的数据结构；为了easy coding，当初的设计者选择了全局大锁；在客户端-服务器处理模型上，选择了一个连接一个进程的&#8220;高耗能&#8221;模式。最初这样的设计应对当年的业务量也是 绰绰有余的，但应付今天的业务规模就显得颇为捉襟见肘了，以至于我们不得不通过罗列机器来满足业务增长的状况。服务器增多，却导致了我们维护 和监控难度的增加。 为了应付现有业务量规模以及未来若干年的业务量增长，我们的新系统的骨架在设计时显然要扬长避短： &#160;&#160;&#160; &#8211; [...]]]></description>
			<content:encoded><![CDATA[<p><i>一切没有目标的努力，都是瞎忙活儿。</i><br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <i>- Tony Bai</i></p>
<p>刚实施回来，就又投入到新工作中，到今天才有那么一点点时间写写这件事儿。</p>
<p><b>* 缘起</b></p>
<p>我们的遗留系统性能一直不高，导致这一局面的因素有很多，比如最初设计和实现的&ldquo;考虑不足&rdquo;、后续维护人员的&ldquo;随波逐流&rdquo;甚至缺少勇气对影响性能的关 键代码进行<a href="http://en.wikipedia.org/wiki/Code_refactoring">重构</a>等等。技术债务就这样一直积累着。直到两年前，我们终见其导致的巨大的影响了。</p>
<p>由于客户方成本压缩，单节点性能低意味着需要更多的硬件投入，并连带着报价升高，导致我们的产品市场竞争力下降。而竞争对手产品的性能是我们的 3-5倍，这终于引起了领导的重视，并下达了开发高性能版本的任务命令。</p>
<p><b>* 抉择</b></p>
<p>遗留系统的问题有很多，性能差仅仅是表象之一。可维护性差更让人印象深刻。遗留系统就像一件打满补丁的旧衣裳，虽然依旧能穿着遮体御寒，但却让我 们时刻战战兢兢，生怕一个动作会导致它解体，变得支离破碎。</p>
<p>对于我们这样一个<a href="http://en.wikipedia.org/wiki/Mission_critical‎">mission-critical</a>的系统来说，开发周期显然是不会短的。在性能达标的同时，更为重要的是保证产品的质量，确保上 线后运行稳定。因此摆在我们面前有两条路：<br />
	&nbsp;&nbsp;&nbsp; 1、在遗留系统上做&ldquo;大修&rdquo; &#8211; 大规模<a href="http://tonybai.com/2006/03/28/c-refactoring/">重构</a>；<br />
	&nbsp;&nbsp;&nbsp; 2、重写，把构成系统的骨架重新设计和实现，使它能够足够坚固，满足在&ldquo;高速公路&rdquo;上驰骋的要求。</p>
<p>我们最终选择了重写，也就是风险较大的那条路。在我们的理解中，重写软件就好比汽车升级平台，就像大众将传统的PQ25、PQ35等统统升级为 MQB平台那样。<b>平台的升级，不光影响技术，还会影响方方面面</b>，比如团队的能力、思维方式、合作模式以及团队过程改善等等。做 得好的话，会使整个团队迈上一个新台阶，这是原地修补所不能够带来的。</p>
<p>对于我个人来说，这也是我期望中的实验田，我将把之前研究的诸多实践落地，帮助团队提升能力。</p>
<p>自私地说，重写系统也是我的一个小理想，能遇到这样一个从无到有构建一个系统的机会是不多的，因此很是希望能看到一个系统一点一点的在自己的呵护 下&ldquo;成长&rdquo;起来。虽然我也清楚完成这样一个系统需要很长时间，而这期间我可能需要时刻紧绷着神经，直到系统正式上线后，才能感受到那一抹释然。</p>
<p><b>* 建立&ldquo;骨架(skeleton)&rdquo;</b></p>
<p>我们将项目分成两个阶段：建立系统&ldquo;骨架&rdquo;和为系统&ldquo;添肉&rdquo;，即添加业务逻辑。</p>
<p>系统的性能目标是原遗留系统的10倍，这样我们建立的骨架的性能至少要高于原遗留系统的10倍。在&ldquo;添肉&rdquo;之前我们要充分证明骨架的设计是合理、 有效、稳定和高性能的。</p>
<p>遗留系统性能低，并非因为当初的设计者能力有什么问题，更多是局限于当初的设计目标。系统初期业务量不大，接入的外部网元不多，因此系统大量使用 了链表这种简单但低效的数据结构；为了easy coding，当初的设计者选择了全局大锁；在客户端-服务器处理模型上，选择了一个连接一个进程的&ldquo;高耗能&rdquo;模式。最初这样的设计应对当年的业务量也是 绰绰有余的，但应付今天的业务规模就显得颇为捉襟见肘了，以至于我们不得不通过罗列机器来满足业务增长的状况。服务器增多，却导致了我们维护 和监控难度的增加。</p>
<p>为了应付现有业务量规模以及未来若干年的业务量增长，我们的新系统的骨架在设计时显然要扬长避短：<br />
	&nbsp;&nbsp;&nbsp; &#8211; 我们重新设计了通用的服务端框架和客户端框架，使得系统各个业务模块采用相同的通信处理机制；<br />
	&nbsp;&nbsp;&nbsp; &#8211; 我们没有选择线程，而是依旧采用成熟的进程（资源隔离式） + IO多路复用（linux下epoll机制）的服务器-客户端模型，与以往不同的是，我们在每个进程中处理多个链接，设定进程数量在合理水平，避免大量上下文切换带来的性能损耗；<br />
	&nbsp;&nbsp;&nbsp; &#8211; 将传统的全局big lock更换成了细粒度锁；<br />
	&nbsp;&nbsp;&nbsp; &#8211; 采用高效的数据结构和算法，比如用hash和array替代掉list等；<br />
	&nbsp;&nbsp;&nbsp; &#8211; 用简单队列替换掉原先复杂的队列调度结构，降低代码理解难度和后续维护门槛。<br />
	&nbsp;&nbsp;&nbsp; &#8211; &#8230; &#8230;</p>
<p>我们要求对骨架代码进行严格的<a href="http://tonybai.com/2005/11/08/the-design-and-implementation-of-c-unittest-framework/">单元测试</a>，通过<a href="http://tonybai.com/2010/09/30/opensource-a-lightweight-c-unit-test-framework/">lcut</a>为骨架代码建立起单元测试集，并结合持续集成对骨架代码进行持续的单元测试验证。</p>
<p>骨架完成后，我们对其进行了全面的压力测试，确保其性能水平达到我们设计要求，这是我们进入下一阶段的前提条件。</p>
<p><b>* 添肉(business logic)</b></p>
<p>有了稳定、可靠、高效的骨架，我们在&rdquo;添肉&ldquo;阶段就更加有信心了。用C写纯业务逻辑是苦逼了一些，但还好我们没有全部将以前遗留代码扔掉，我们为了保证功 能Feature不丢失，我们会尽量复用之前的业务逻辑，当然是&ldquo;规范地&rdquo;搬到新系统中的，尽可能地去除原有代码中的<a href="http://tonybai.com/2010/12/06/do-not-provide-soil-for-bad-smell-code/">Bad smell</a>。</p>
<p>与骨架相比，业务逻辑相对复杂，且耦合较多，因此对这些业务逻辑做单元测试真是一件让人头疼的事情。不过这也和我们最初的估计相符，最初制定的策略就是对骨架代码做高覆盖，对业务代码则宽松些，尽量覆盖即可。</p>
<p><b>* 附加实践</b></p>
<p>就像前面所说的那样，围绕着这次重写系统，我策划了很多实践有了落脚之地，包括：<br />
	&nbsp;&nbsp;&nbsp; &#8211; 试点<a href="http://tonybai.com/2011/11/23/those-things-about-knowledge-management/">知识管理</a> ：通过这次重写，建立起关于该系统的知识库；<br />
	&nbsp;&nbsp;&nbsp; &#8211; 增加基于<a href="http://tonybai.com/2011/03/04/some-experience-on-using-review-board/">ReviewBoard</a>的<a href="http://tonybai.com/2010/12/18/thoughts-on-online-coding-review/">在线代码评审</a>环节；<br />
	&nbsp;&nbsp;&nbsp; &#8211; 引入基于<a href="http://tonybai.com/2012/02/14/install-and-configure-jenkins/">Jenkins</a>的<a href="http://tonybai.com/2012/02/15/intergating-on-multiple-platforms-simultaneously-using-jenkins/">持续集成</a>；<br />
	&nbsp;&nbsp;&nbsp; &#8211; 重新思考和设计<a href="http://tonybai.com/2012/01/17/also-talk-about-building-c-app/">构建</a>环节，通过<a href="http://tonybai.com/2011/12/08/buildc-a-building-assistant-tool-for-c-app/">buildc</a>提高构建效率；<br />
	&nbsp;&nbsp;&nbsp; &#8211; 重新设计通用<a href="http://tonybai.com/2012/02/01/also-talk-about-c-app-install-package-making-and-deploying/">安装包</a>；<br />
	&nbsp;&nbsp;&nbsp; &#8211; 使用<a href="http://tonybai.com/2010/09/30/opensource-a-lightweight-c-unit-test-framework/">LCUT</a>对骨架进行单元测试覆盖；<br />
	&nbsp;&nbsp;&nbsp; &#8211; 规范<a href="http://tonybai.com/2013/05/09/also-talk-about-commit-log/">commit log</a>以及<a href="http://tonybai.com/2013/07/08/code-review-from-rule-of-man-to-rule-of-law/">代码提交流程</a>；<br />
	&nbsp;&nbsp;&nbsp; &#8211; 应用<a href="http://tonybai.com/2011/04/21/apply-style-check-to-c-code/">代码风格检查</a>工具，使得所有代码风格一致。</p>
<p>事实证明上述实践在这次系统重写的过程中产生了很好的效果，尤其在代码质量保证方面，系统上线后的结果也恰恰印证了这一点。</p>
<p><b>* 上线</b></p>
<p>&ldquo;丑媳妇总要见公婆&rdquo;。我们的新系统也到了该上线服务的时候了。为了这次上线，我们做了较为充分的实施准备，无论是人员还是时间，都有倾向性的向这个系统 投入。我们也提前做好了应对各种突发问题的预案。可实际情况出乎预料，与遗留系统的版本升级相比，这次全新系统上线显得十分顺利，系统的核心相当稳定，出 现的一些问题也都比较边缘，对这次成功上线已经不构成什么影响了。</p>
<p><b>* 那一抹释然</b></p>
<p>在实施人员庆贺上线成功时，在领导口头表扬时，我的内心却显得十分平静。对于新系统来说，这是一个好的开始。对我个人来说，我感受到了那一抹期望已久的释 然。在这个领域里这个方向上已经摸爬滾打了多年，虽然还有好多地方需要改进，好多实践需要完善，但我的内心告诉我：&ldquo;够了&rdquo;、&ldquo;已经没什么牵挂了&rdquo;、&ldquo;是 时候换换方向、换换领域了&rdquo;、&ldquo;让其他人去做吧&rdquo;。我已经在产品和团队中融入了我的思想，我相信他们都能很好的演化和发展。而我则为接受新思想、新领域做 好了准备。</p>
<p>的确也到了为自己设立新目标的时候了！</p>
<p style='text-align:left'>&copy; 2013, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2013/12/26/just-for-being-relieved/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>buildc 0.3.0版本发布</title>
		<link>https://tonybai.com/2013/05/11/buildc-0-3-0-release/</link>
		<comments>https://tonybai.com/2013/05/11/buildc-0-3-0-release/#comments</comments>
		<pubDate>Sat, 11 May 2013 00:46:42 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[Buildc]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[CBehave]]></category>
		<category><![CDATA[Git]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[LCUT]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Subversion]]></category>
		<category><![CDATA[学习]]></category>
		<category><![CDATA[工作]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[程序员]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1268</guid>
		<description><![CDATA[自buildc正式在项目中应用以来，我们收到了许多同事针对buildc演进的意见和建议。其中确实有些易用性的问题是在最初设计时未考虑周全的，尤其是.buildc.rc中的配置，同事们对该文件的配置已经&#8220;怨声载道&#8221;了。 .buildc.rc是用来配置某开发者在开发过程中使用的第三方库所在subversion repository信息的，例如： a_repository = (&#39;SVN库地址&#39;, &#39;本地缓存路径&#39;, &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; [ &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; # 格式：[(&#8220;第三方库名称&#8221;, &#8220;库版本&#8221;, &#8220;特征库文件&#8221;), &#8230;] &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; (&#39;libevent&#39;, &#39;2.0.10&#39;, &#39;lib/libevent.a&#39;), &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; (&#39;instantclient&#39;, &#39;10.2.0.5.0&#39;, &#39;lib/libnnz10.so&#39;), &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#8230; &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ] &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ) b_repository = (&#39;SVN库地址&#39;, &#39;本地缓存路径&#39;, []) c_repository = (&#39;SVN库地址&#39;, &#39;本地缓存路径&#39;, []) &#8230; external_repositories = [ &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; a_repository, &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; b_repository, &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; c_repository, &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#8230; &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ] 这里面需要维护最多、最频繁的就是各个repository中具备的第三方库名、版本号。开发者所开发的项目所依赖的第三方库信息发生变化，不仅仅需要修 改project下的buildc.cfg文件，还可能要修改.buildc.rc，大家维护起来确实体验不好，会多耗费一些工作量。 [...]]]></description>
			<content:encoded><![CDATA[<p>自<a href="http://tonybai.com/2011/12/08/buildc-a-building-assistant-tool-for-c-app/">buildc</a>正式在项目中应用以来，我们收到了许多同事针对<a href="http://code.google.com/p/buildc">buildc</a>演进的意见和建议。其中确实有些易用性的问题是在最初设计时未考虑周全的，尤其是.buildc.rc中的配置，同事们对该文件的配置已经&ldquo;怨声载道&rdquo;了。</p>
<p>.buildc.rc是用来配置某开发者在开发过程中使用的第三方库所在subversion repository信息的，例如：</p>
<p><font face="Courier New">a_repository = (&#39;SVN库地址&#39;, &#39;本地缓存路径&#39;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # 格式：[(&ldquo;第三方库名称&rdquo;, &ldquo;库版本&rdquo;, &ldquo;特征库文件&rdquo;), &hellip;]<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (&#39;libevent&#39;, &#39;2.0.10&#39;, &#39;lib/libevent.a&#39;),<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (&#39;instantclient&#39;, &#39;10.2.0.5.0&#39;, &#39;lib/libnnz10.so&#39;),<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ]<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; )<br />
	b_repository = (&#39;SVN库地址&#39;, &#39;本地缓存路径&#39;, [])<br />
	c_repository = (&#39;SVN库地址&#39;, &#39;本地缓存路径&#39;, [])<br />
	&hellip;<br />
	external_repositories = [<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a_repository,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b_repository,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c_repository,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &hellip;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ]</font></p>
<p>这里面需要维护最多、最频繁的就是各个repository中具备的第三方库名、版本号。开发者所开发的项目所依赖的第三方库信息发生变化，不仅仅需要修 改project下的buildc.cfg文件，还可能要修改.buildc.rc，大家维护起来确实体验不好，会多耗费一些工作量。</p>
<p>针对这个主要问题，我们决定对buildc进行一次较大范围的重构，重构后的版本定为<a href="https://buildc.googlecode.com/files/buildc-0.3.0.tar.gz">buildc 0.3.0版本</a>。以下是buildc 0.3.0版本的主要改动点：</p>
<p><b>一、简化.buildc.rc的配置，重新定义cache相关命令的语义</b></p>
<p>0.3.0及以后版本的.buildc.rc只需配置repository的地址信息以及cache缓存的本地路径信息，无需再提供repository 里面具体的第三方库以及版本号信息了，这样一来，大多数情况下，project依赖的第三方库发生变更，都无需修改.buildc.rc了。</p>
<p><font face="Courier New">a_repository = (&#39;SVN库地址&#39;, &#39;本地缓存路径&#39;)<br />
	b_repository = (&#39;SVN库地址&#39;, &#39;本地缓存路径&#39;)<br />
	c_repository = (&#39;SVN库地址&#39;, &#39;本地缓存路径&#39;)<br />
	&hellip;<br />
	external_repositories = [<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a_repository,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b_repository,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c_repository,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &hellip;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ]</font></p>
<p>随之而变的是buildc cache相关命令的语义，0.3.0中cache相关命令的语义如下：</p>
<p><i>* buildc cache init </i>- 生成.buildc.repository，该文件是svn库的目录结构文件，相当于一份svn repository内部的地图，repository中存放的各种第三方库以及版本均在该文件中索引；如果该文件已经存在，命令执行的结果为：提示已存在。</p>
<p><i>* buildc cache upgrade</i> &#8211; 根据.buildc.rc的最新更新，重新生成.buildc.repository文件，并将该文件中所有lib本地的 Revision号置为none。该文件并不会执行本地cache的library的真实更新操作。</p>
<p><i>* buildc cache update</i>&nbsp; -&nbsp;<br />
	&nbsp;&nbsp;&nbsp; 1. 如果.buildc.rc已经修改，但没有执行buildc cache upgrade，update会对比本地缓存库信息与对应的.repository文件中的同名lib信息，如果不一致，则提醒执行upgrade。<br />
	&nbsp;&nbsp;&nbsp; 2. 如果.buildc.repository是新生成的，所有lib本地的Revision号均是none，则提示没有要更新的本地缓存库；<br />
	&nbsp;&nbsp;&nbsp; 3. 如果某个项目已经download了自己依赖的库，那update将比对svn库中和本地库的revision差异，并下载最新库版本。并修改.buildc.repository中对应库的本地revision number。</p>
<p>* <i>buildc cache remove</i> &#8211; 将.buildc.repository中对应库的本地revision number都置为none，并删除本地缓存的库文件。</p>
<p><b>二、重新定义config make的语义</b></p>
<p>前面提到了，在执行buildc cache init时，buildc只是负责生成.repository文件，而并不真实执行库文件的下载和缓存。那何时真正下载呢？答案是在执行buildc config make时。这里颇有些&ldquo;lazy evaluate&rdquo;的味道，需要时再&ldquo;download and cache it&quot;。</p>
<p>* <em>buildc config make</em></p>
<p>1. 如果.buildc.rc已经修改，但没有执行buildc cache upgrade，config make会对比本地缓存库信息与对应的.repository文件中的同名lib信息，如果不一致，则提醒执行upgrade。<br />
	2. 如果.buildc.rc是新生成的，或执行cache upgrade后的，config make会根据project对应的buildc.cfg中配置的第三方库，在.buildc.repository中查找是否存在（包括对应的版本 号），如果存在，则从subversion server端自动下载；否则提示出错。<br />
	3. 如果本地缓存中某个库文件不存在，buildc config make会检测到，并自动下载该库，并cache起来。<br />
	4. 如果subversion端某个库的svn revision号发生的更新，buildc config make会检测到，并下载最新的版本。</p>
<p>总之一切都是在buildc config make时来完成的，按需下载或更新，这样你甚至无需进行手工的library Cache维护。</p>
<p><b>三、转向OO</b><b>范型</b></p>
<p>实现buildc 0.3.0的小同事(wtz1989227@gmail.com)对OO情有独钟，因此在这个版本中，他将以前的结构化代码做了大幅度调整，并用OO的方 式进行了重构。按照wtz的思路，这次改造比较初级，OOD做得还不够充分，以后慢慢调整。实际代码中反映出来的情况也的确是这样。</p>
<p><b>四、</b><b>buildc 0.2.3发布</b></p>
<p>在将buildc 0.3.0代码merge到trunk之前，我创建了buildc-0.2的maintain branch，虽然理论上buildc 0.3.0在功能和配置方面与buildc 0.2.x版本是兼容的，但毕竟代码调整幅度较大。另外建议大家都转移到0.3.0这个最新版本上来，buildc-0.2分支顶多做一些bugfix， 不会再有新feature添加进去了。</p>
<p>昨天在发布buildc 0.3.0的同时，还发布了buildc-0.2的一个Bugfix版 &#8211; <a href="https://buildc.googlecode.com/files/buildc-0.2.3.tar.gz">buildc 0.2.3</a>，该版本主要做了如下一些fix：</p>
<p>&nbsp; * 执行cache upgrade时增加对.buildc.rc中repository特征文件存在性的检查；<br />
	&nbsp; * 执行config make时增加对Make.rules文件是否为空的判断；<br />
	&nbsp; * 执行pack source时，添加VERSION文件，记录打包的上下文信息。</p>
<p><b>五、其它</b></p>
<p>考虑到<a href="http://github.com">github</a>的活跃度远远高于<a href="http://code.google.com">google code</a>，加上google code最近访问十分不稳定，因此之前就将<a href="https://github.com/bigwhite/buildc">buildc</a>（还有<a href="https://github.com/bigwhite/cbehave">cbehave</a>、<a href="https://github.com/bigwhite/lcut">lcut</a>以及我的<a href="https://github.com/bigwhite/experiments">实验代码库</a>）fork了一份到github上了，也攒攒 github上人气，因此这次buildc 0.2.3和buildc 0.3.0的代码还要发布到github上一次。git工具平时用的少，尤其是提交代码到github，这次算入门了。</p>
<p>* 代码远程提交<br />
	用git remote add一个github的remote repository后，就可以使用git push origin master将本地的commit推送到github上了。</p>
<p>* 打tag，并推送tag</p>
<p>&nbsp;&nbsp; &#8212; 查看Tag的git命令是<font face="Courier New">git tag</font>；<br />
	&nbsp;&nbsp; &#8212; 本地打tag，用这个命令： <font face="Courier New">git tag -a v0.2.3 -m&quot;0.2.3 released&quot;</font>；<br />
	&nbsp;&nbsp; &#8212; 推送Tag到remote repository：<font face="Courier New">git push &#8211;tags origin master</font>，不加&#8211;tags是无法推送tag的。</p>
<p>* branch操作<br />
	&nbsp; &#8212; 查看branch：<font face="Courier New">git branch</font><br />
	&nbsp; &#8212; 创建branch：<font face="Courier New">git branch buildc-0.2</font><br />
	&nbsp; &#8212; 推送branch：<font face="Courier New">git push origin buildc-0.2</font><br />
	&nbsp; &#8212; 本地切换branch：<font face="Courier New">git checkout buildc-0.2</font></p>
<p style='text-align:left'>&copy; 2013, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2013/05/11/buildc-0-3-0-release/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>buildc 0.1.5版本发布</title>
		<link>https://tonybai.com/2012/04/13/buildc-0-1-5-release/</link>
		<comments>https://tonybai.com/2012/04/13/buildc-0-1-5-release/#comments</comments>
		<pubDate>Fri, 13 Apr 2012 14:34:08 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[Buildc]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[CI]]></category>
		<category><![CDATA[GNU]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[Jenkins]]></category>
		<category><![CDATA[LCUT]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Packing]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Setup]]></category>
		<category><![CDATA[Subversion]]></category>
		<category><![CDATA[单元测试]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[安装包]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[持续集成]]></category>
		<category><![CDATA[构建]]></category>
		<category><![CDATA[程序员]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=869</guid>
		<description><![CDATA[这两天对buildc的改动比较频繁，今天又修正了一些问题，也增加了一些小功能。主要包括这么几点： &#160; 1、在Make.rules.in中增加了STATIC_LIBS和DYNAMIC_LIBS &#160; 项目源代码和项目中单元测试代码使用同一个Make.rules，也此编译时也就共享同一个LIBS变量。对于静态共享库还好说，但对于动态共享库，诸如Oracle的instantclient库，单元测试代码中即使没有使用到动态共享库中的接口，也要对该动态共享库产生一个依赖。这样在执行单元测试用例时就会因无法寻得动态共享库而导致用例执行失败。 &#160; 为此，我在Make.rules.in中增加了STATIC_LIBS和DYNAMIC_LIBS两个变量，即将原LIBS变量中的静态共享库和动态共享库分开，分别放入STATIC_LIBS和DYNAMIC_LIBS中。然后让项目中单元测试代码的编译只依赖STATIC_LIBS，上述问题就得到了解决(如果你的单元测试真实需要链接动态共享库，那就另当别论了)。 &#160; 2、添加system_libs，并进一步明确了external_libs、custom_libs和新增的system_libs的含义 &#160; buildc设计之初，设计了三种lib：external_libs、custom_libs和default_libs，最初的设想是这样的： &#160; &#160; * external_libs &#8211; 一般配置第三方库或组织内部公共库； &#160; * custom_libs &#8211; 项目相关的C运行库和*nix系统库依赖库，或一些项目内部实现的库； &#160; * default_libs &#8211; C后台应用一般都需要链接的C运行库和*nix系统库，惯例优于配置，直接写死在代码中。 &#160; 但实际运用时发现，custom_libs如果既配置C运行库或*nix系统库依赖库，又配置一些项目内部实现的库，会存在静态共享库依赖顺序问题，另外custom_libs与external_libs之间也会因此而存在库链接顺序之问题。而default_libs目前为空，因为很难找到各个项目的一般依赖。 &#160; 于是这次我对这个设计进行了一些修正，增加了SYSTEM_LIBS，并进一步明确了这些lib配置的含义，依顺序如下： &#160; &#160; * custom_libs &#8211; 一般配置项目自实现、自用的库，可能包含在项目源码库内部，与项目源码库一并发布； &#160; * external_libs &#8211; 一般配置第三方库或组织内部的公共库； &#160; * system_libs &#8211; 用来替代default_libs，配置项目需要的C运行时库或*nix系统库，放在所有库的最后面。 &#160; default_libs似乎没有太大必要了，后续也许会从代码中remove出去。 &#160; 3、增加cache upgrade &#160; 通过实践发现，目前buildc提供的对本地缓存的Library的管理手段还有欠缺，特别是当.buildc.rc发生变更时，需要执行buildc cache [...]]]></description>
			<content:encoded><![CDATA[<p>这两天对<a href="http://code.google.com/p/buildc">buildc</a>的改动比较频繁，今天又修正了一些问题，也增加了一些小功能。主要包括这么几点：</p>
<div>&nbsp;</div>
<div>1、在Make.rules.in中增加了STATIC_LIBS和DYNAMIC_LIBS</div>
<div>&nbsp;</div>
<div>项目源代码和项目中<a href="http://tonybai.com/2010/09/30/opensource-a-lightweight-c-unit-test-framework/">单元测试</a>代码使用同一个Make.rules，也此编译时也就共享同一个LIBS变量。对于静态<a href="http://tonybai.com/2010/12/13/also-talk-about-shared-library/">共享库</a>还好说，但对于动态共享库，诸如Oracle的instantclient库，单元测试代码中即使没有使用到动态共享库中的接口，也要对该动态共享库产生一个依赖。这样在执行单元测试用例时就会因无法寻得动态共享库而导致用例执行失败。</div>
<div>&nbsp;</div>
<div>为此，我在Make.rules.in中增加了STATIC_LIBS和DYNAMIC_LIBS两个变量，即将原LIBS变量中的静态共享库和动态共享库分开，分别放入STATIC_LIBS和DYNAMIC_LIBS中。然后让项目中单元测试代码的编译只依赖STATIC_LIBS，上述问题就得到了解决(如果你的单元测试真实需要链接动态共享库，那就另当别论了)。</div>
<div>&nbsp;</div>
<div>2、添加system_libs，并进一步明确了external_libs、custom_libs和新增的system_libs的含义</div>
<div>&nbsp;</div>
<div>buildc设计之初，设计了三种lib：external_libs、custom_libs和default_libs，最初的设想是这样的：</div>
<div>&nbsp;</div>
<div>&nbsp; * external_libs &#8211; 一般配置第三方库或组织内部公共库；</div>
<div>&nbsp; * custom_libs &#8211; 项目相关的C运行库和*nix系统库依赖库，或一些项目内部实现的库；</div>
<div>&nbsp; * default_libs &#8211; C后台应用一般都需要链接的C运行库和*nix系统库，惯例优于配置，直接写死在代码中。</div>
<div>&nbsp;</div>
<div>但实际运用时发现，custom_libs如果既配置C运行库或*nix系统库依赖库，又配置一些项目内部实现的库，会存在静态<a href="http://tonybai.com/2010/10/11/start-with-mock-malloc/">共享库依赖顺序问题</a>，另外custom_libs与external_libs之间也会因此而存在库链接顺序之问题。而default_libs目前为空，因为很难找到各个项目的一般依赖。</div>
<div>&nbsp;</div>
<div>于是这次我对这个设计进行了一些修正，增加了SYSTEM_LIBS，并进一步明确了这些lib配置的含义，依顺序如下：</div>
<div>&nbsp;</div>
<div>&nbsp; * custom_libs &#8211; 一般配置项目自实现、自用的库，可能包含在项目源码库内部，与项目源码库一并发布；</div>
<div>&nbsp; * external_libs &#8211; 一般配置第三方库或组织内部的公共库；</div>
<div>&nbsp; * system_libs &#8211; 用来替代default_libs，配置项目需要的C运行时库或*nix系统库，放在所有库的最后面。</div>
<div>&nbsp;</div>
<div>default_libs似乎没有太大必要了，后续也许会从代码中remove出去。</div>
<div>&nbsp;</div>
<div>3、增加cache upgrade</div>
<div>&nbsp;</div>
<div>通过实践发现，目前buildc提供的对本地缓存的Library的管理手段还有欠缺，特别是当.buildc.rc发生变更时，需要执行buildc cache remove和buildc cache init才能正确完成更新，稍显繁琐，因此今天给buildc增加了一个cache upgrade命令，用于改善这个情况。而buildc cache update一般仅用于.buildc.rc的库配置没有改变，但subversion库中的库二进制文件被更新(比如重新制作了)的情况。这样看来还是.buildc.rc变更的情况常见些，比如某个库的版本升级了(例如，<a href="http://code.google.com/p/lcut">lcut</a>从0.2.0升级为<a href="http://tonybai.com/2012/04/10/lcut-0-3-0-release/">0.3.0</a>)，或某个库的位置发生了变化，或删减了某些库或新增了某些库等等。</div>
<div>&nbsp;</div>
<div>以上就是<a href="http://buildc.googlecode.com/files/buildc-0.1.5.tar.gz">buildc 0.1.5</a>版本的所有改动了。</div>
<p style='text-align:left'>&copy; 2012, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2012/04/13/buildc-0-1-5-release/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>lcut 0.3.0版本发布</title>
		<link>https://tonybai.com/2012/04/10/lcut-0-3-0-release/</link>
		<comments>https://tonybai.com/2012/04/10/lcut-0-3-0-release/#comments</comments>
		<pubDate>Tue, 10 Apr 2012 13:15:27 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[autoconf]]></category>
		<category><![CDATA[automake]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[Cmockery]]></category>
		<category><![CDATA[Configure]]></category>
		<category><![CDATA[GNU]]></category>
		<category><![CDATA[LCUT]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Makefile]]></category>
		<category><![CDATA[Mock]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[Unittest]]></category>
		<category><![CDATA[单元测试]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[程序员]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=853</guid>
		<description><![CDATA[lcut单元测试框架在我的项目中应用已经有一段时间了，项目组的同事对lcut的使用也是越来越熟悉，这不今天一位同事还提出了一个新需求，需求大致是这样的。 &#160; 在实际项目中，经常遇到这类情况： &#160; int bar(&#8230;) { &#160; &#160; int ret; &#160; &#160; &#160; ret = foo(&#8230;); &#160; &#160; /* assert ret */ &#160; &#160; &#8230; &#160; &#160; &#160; ret = foo(&#8230;); &#160; &#160; /* assert ret */ &#160; &#160; &#8230; &#160; &#160; &#160; ret = foo(&#8230;); &#160; &#160; /* assert ret */ &#160; &#160; [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://code.google.com/p/lcut">lcut</a>单元测试框架在我的项目中应用已经有一段时间了，项目组的同事对<a href="http://tonybai.com/2010/09/30/opensource-a-lightweight-c-unit-test-framework/">lcut</a>的使用也是越来越熟悉，这不今天一位同事还提出了一个新需求，需求大致是这样的。</p>
<div>&nbsp;</div>
<div>在实际项目中，经常遇到这类情况：</div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">int bar(&#8230;) {</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; int ret;</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; ret = foo(&#8230;);</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; /* assert ret */</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; &#8230;</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; ret = foo(&#8230;);</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; /* assert ret */</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; &#8230;</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; ret = foo(&#8230;);</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; /* assert ret */</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; &#8230;</span></div>
<div><span style="font-family:courier new,courier,monospace;">}</span></div>
<div>&nbsp;</div>
<div>上述代码中被测函数接口bar的实现中多次调用了某函数foo。这样当我们用<a href="http://tonybai.com/2010/10/29/lcut-add-mock-support/">mock</a>方式测试bar这个函数时，可能需要多次重复设置foo的返回值以及输出函数的值，就像这样：</div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">void tc_test_bar_return_ok(lcut_tc_t *tc, void *data) {</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; LCUT_RETV_RETURN(foo, 0);</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; LCUT_RETV_RETURN(foo, 0);</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; LCUT_RETV_RETURN(foo, 0);</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; LCUT_ARG_RETURN(foo, 1);</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; LCUT_ARG_RETURN(foo, 1);</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; LCUT_ARG_RETURN(foo, 1);</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; LCUT_INT_EQUAL(tc, 0, bar(&#8230;));</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; &#8230;</span></div>
<div><span style="font-family:courier new,courier,monospace;">}</span></div>
<div>&nbsp;</div>
<div>我的同事希望lcut能提供一个接口：支持一次调用，设置多次mock obj的返回值或输出参数，使用这样的接口后，上述代码就可以简化为：</div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">void tc_test_bar_return_ok(lcut_tc_t *tc, void *data) {</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; LCUT_RETV_RETURN_COUNT(foo, 0, 3);</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; LCUT_ARG_RETURN_COUNT(foo, 1, 3);</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; LCUT_INT_EQUAL(tc, 0, bar(&#8230;));</span></div>
<div><span style="font-family:courier new,courier,monospace;">}</span></div>
<div>&nbsp;</div>
<div>这个需求提的非常好，看起来更像是一种语法糖(<a href="http://en.wikipedia.org/wiki/Syntactic_sugar">syntactic sugar</a>)，用于简化代码编写。于是乎下午我就为lcut增加了两个有用的宏：LCUT_RETV_RETURN_COUNT和LCUT_ARG_RETURN_COUNT。</div>
<div>&nbsp;</div>
<div>正如上面所说，这两个宏可在一次调用中多次设置某个mock obj的返回值和输出参数值，两个宏的原型如下：</div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">#define LCUT_RETV_RETURN_COUNT(fcname, value, count) do { \</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; &nbsp; &nbsp; lcut_mock_obj_return(#fcname, (void*)value, __FUNCTION__, __LINE__, __FILE__, MOCK_RETV, count); \</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; } while(0);</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">#define LCUT_ARG_RETURN_COUNT(fcname, value, count) do { \</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; &nbsp; &nbsp; lcut_mock_obj_return(#fcname, (void*)value, __FUNCTION__, __LINE__, __FILE__, MOCK_ARG, count); \</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; } while(0);</span></div>
<div>&nbsp;</div>
<div>只是比之前提供的LCUT_RETV_RETURN和LCUT_ARG_RETURN多了一个宏参数count。count用于指出对mocked obj进行多少次返回值或输出参数的设置。</div>
<div>&nbsp;</div>
<div>另外当count传入-1时，其语义为无论mocked object被使用多少次，其返回值或输出参数值都是一样的，即使用LCUT_RETV_RETURN_COUNT或LCUT_ARG_RETURN_COUNT时设置的那个值，直到下一次调用这两个宏进行重新设置时，值才会发生变化。例如上面的例子我们也可以改写为：</div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">void tc_test_bar_return_ok(lcut_tc_t *tc, void *data) {</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; LCUT_RETV_RETURN_COUNT(foo, 0, -1);</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; LCUT_ARG_RETURN_COUNT(foo, 1, -1);</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; LCUT_INT_EQUAL(tc, 0, bar(&#8230;));</span></div>
<div><span style="font-family:courier new,courier,monospace;">}</span></div>
<div>&nbsp;</div>
<div>这样无论后续再调用多少次bar函数，foo的返回值总是0，输出参数也总是1。</div>
<div>&nbsp;</div>
<div>增加了这两个宏后，lcut的版本号也小升了一位，最新版本是<a href="http://lcut.googlecode.com/files/lcut-0.3.0-rc1.tar.gz">lcut-0.3.0-rc1</a>，其中还增加了一个针对lcut mock功能的example &#8211; mock_test.c。同时Google Code上的<a href="http://code.google.com/p/lcut/wiki/lcut_user_guide_cn">lcut guide</a>也做了更新，对新增的宏的用法进行了简要说明。</div>
<div>&nbsp;</div>
<div>就这样，lcut 0.3.0版本算是发布了，后续还会经过内部的细致测试，如果没有什么问题，就会去掉rc。</div>
<p style='text-align:left'>&copy; 2012, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2012/04/10/lcut-0-3-0-release/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>CBehave &#8211; 一个C语言行为驱动开发框架</title>
		<link>https://tonybai.com/2011/08/15/cbehave-a-bdd-framework-for-c/</link>
		<comments>https://tonybai.com/2011/08/15/cbehave-a-bdd-framework-for-c/#comments</comments>
		<pubDate>Mon, 15 Aug 2011 05:18:00 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[BDD]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[CBehave]]></category>
		<category><![CDATA[CSpec]]></category>
		<category><![CDATA[JBehave]]></category>
		<category><![CDATA[JUnit]]></category>
		<category><![CDATA[LCUT]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[TDD]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[学习]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[编码]]></category>
		<category><![CDATA[行为驱动开发]]></category>

		<guid isPermaLink="false">http://tonybai.com/2011/08/15/cbehave-%e4%b8%80%e4%b8%aac%e8%af%ad%e8%a8%80%e8%a1%8c%e4%b8%ba%e9%a9%b1%e5%8a%a8%e5%bc%80%e5%8f%91%e6%a1%86%e6%9e%b6/</guid>
		<description><![CDATA[<p>Behaviour-Driven Development，即行为驱动开发在业界早已不是什么新鲜玩意了。我之前也 略有了解，不过一直没有"深入钻研"。直到今年年初InfoQ的几篇有关BDD的文章才让我对BDD有了更多的认识。与TDD一样，C语言在BDD领域依旧是一个"后进分子"，在多数主流语言(Java...</p>]]></description>
			<content:encoded><![CDATA[<p><a href="http://behaviour-driven.org/" target="_blank">Behaviour-Driven Development</a>，即行为驱动开发在业界早已不是什么新鲜玩意了。我之前也略有了解，不过一直没有&quot;深入钻研&quot;。直到今年年初InfoQ的几篇有关BDD的文章才让我对BDD有了更多的认识。与<a href="http://en.wikipedia.org/wiki/Test-driven_development" target="_blank">TDD</a>一样，C语言在BDD领域依旧是一个&quot;后进分子&quot;，在多数主流语言(Java，C#，Ruby等)都已经拥有比较成熟的BDD框架(如<a href="http://jbehave.org/" target="_blank">JBehave</a>、<a href="http://specflow.org/" target="_blank">SpecFlow</a>和<a href="http://cukes.info/" target="_blank">Cucumber</a>)的今天，C语言却似乎仅有一款BDD框架-<a href="https://github.com/arnaudbrejeon/cspec" target="_blank">CSpec</a>可用。于是年初的时候我就把设计和实现一个用于C语言的行为驱动开发框架加入到我今年的ToDoList中了。</p>
<p>在确定好目标的同时，我也给这款框架命名为<a href="http://code.google.com/p/cbehave" target="_blank">CBehave</a>(模仿JBehave)，并在Google Code上建立了CBehave的托管项目。但人的时间和精力总是有限的，直到8月中旬我才开始着手进行这个框架的设计和实现。设计和实现一款给程序员使用的工具，这本身就是一件让人兴奋的事情。我先是通过DanNorth的博文&quot;<a href="http://dannorth.net/introducing-bdd/" target="_blank">Introducing BDD</a>&quot;(其中译版在<a href="http://tonybai.com/2011/08/10/introducing-bdd/" target="_blank">这里</a>)了解了BDD的&quot;诞生历程&quot;，然后又广泛地了解了一下其他语言的BDD框架。对于CSpec这一目前唯一的C语言BDD框架，我并不想给予过多评价，不过总体来说和其他语言的BDD框架相比，CSpec有些简陋，应该说还无法很好的支持BDD中一些核心思想的表达，并且目前它还不支持<a href="http://tonybai.com/2008/04/12/mock-test-in-c-unit-test/" target="_blank">Mock</a>。这些都坚定了我重新实现一个C语言BDD框架的决心，起码我不完全是&quot;重新发明轮子&quot;^_^。</p>
<p>作为后来者，CBehave的设计参考了诸多现有的主流BDD框架，其中直接灵感来源于Cucumber，不过由于C语言静态编译语言的本质，CBehave与Cucumber在大多地方也只是形似而已。作为一篇CBehave的介绍性文章，这里列举一些CBehave的主要特点：</p>
<p>首先，CBehave借鉴Cucumber的设计采用Feature + Scenario结合的方式来描述功能需求(DanNorth: 需求也是行为)，并且在每个Scenario内部采用BDD的经典的GIVEN-WHEN-THEN结构描述行为的验收标准(acceptance criteria)。</p>
<p>这里给出一个总体的行为描述模板：</p>
<p>FEATURE #1<br />
	&nbsp;&nbsp;&nbsp; SCENARIO #1<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GIVEN<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WHEN<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; THEN<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;</p>
<p>&nbsp;&nbsp;&nbsp; SCENARIO #2<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GIVEN<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WHEN<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; THEN<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;</p>
<p>&nbsp;&nbsp;&nbsp; &#8230; &#8230;</p>
<p>FEATURE #2<br />
	&#8230; &#8230;&nbsp;</p>
<p>FEATURE #n</p>
<p>原则上FEATURE之间是相互隔离的；FEATURE内部的多个Scenario之间在代码定义和执行时也是相互隔离，互不干扰的。这是一个使用该框架的基本约束。不过这就好比建议性锁，全靠使用时的自觉，否则很容易造成框架运行出错。</p>
<p>下面是一个真实的使用CBehave对strstr函数进行测试的例子(代码片断，完整例子参见源码cbehave/src/example/string_test.c)：</p>
<p>FEATURE(1, &quot;strstr&quot;)<br />
	&nbsp;&nbsp;&nbsp; SCENARIO(&quot;The strstr finds the first occurrence of the substring in the source string&quot;)</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GIVEN(&quot;A source string: [Lionel Messi is a great football player]&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char *str = &quot;Lionel Messi is a great football player&quot;;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GIVEN_END</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WHEN(&quot;we use strstr to find the first occurrence of [football]&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char *p = strstr(str, &quot;football&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WHEN_END</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; THEN(&quot;We should get the string: [football player]&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SHOULD_STR_EQUAL(p, &quot;football player&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; THEN_END</p>
<p>&nbsp;&nbsp;&nbsp; SCENARIO_END</p>
<p>&nbsp;&nbsp;&nbsp; SCENARIO(&quot;If strstr could not find the first occurrence of the substring, it will return NULL&quot;)</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GIVEN(&quot;A source string: FC Barcelona is a great football club.&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char *str = &quot;FC Barcelona is a great football club&quot;;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GIVEN_END</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WHEN(&quot;we use strstr to find the first occurrence of [AC Milan]&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char *p = strstr(buf, &quot;AC Milan&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WHEN_END</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; THEN(&quot;We should get no string but a NULL&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SHOULD_STR_EQUAL(p, NULL);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; THEN_END<br />
	&nbsp;&nbsp;&nbsp; SCENARIO_END<br />
	FEATURE_END</p>
<p>int main() {<br />
	&nbsp;&nbsp;&nbsp; cbehave_feature strstr_features[] = {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {feature_idx(1)},<br />
	&nbsp;&nbsp;&nbsp; };</p>
<p>&nbsp;&nbsp;&nbsp; return cbehave_runner(&quot;Strstr Features are as belows:&quot;, strstr_features);<br />
	}</p>
<p>编译运行这个测试，我们会得到如下结果(节选)：</p>
<p>Strstr Features are as belows:</p>
<p>Feature: strstr<br />
	&nbsp;Scenario: The strstr finds the first occurrence of the substring in the source string<br />
	&nbsp;&nbsp;Given: A source string: Lionel Messi is a great football player<br />
	&nbsp;&nbsp;When: we use strstr to find the first occurrence of [football]<br />
	&nbsp;&nbsp;Then: We should get the string: [football player]<br />
	&nbsp;Scenario: If strstr could not find the first occurrence of the substring, it will return NULL.<br />
	&nbsp;&nbsp;Given: A source string: FC Barcelona is a great football club.<br />
	&nbsp;&nbsp;When: we use strstr to find the first occurrence of [AC Milan]<br />
	&nbsp;&nbsp;Then: We should get no string but a NULL</p>
<p>Summary:<br />
	&nbsp;total features: [1]<br />
	&nbsp;failed features: [0]<br />
	&nbsp;total scenarios: [2]<br />
	&nbsp;failed scenarios: [0]</p>
<p>CBehave将strstr的行为原汁原味地输出到最终的测试结果中，与xUnit等框架相比，这确是一个进步，我们在获知测试结果的同时，还依稀中看到了这个特性的需求描述，前提是你要给出一个很好很精确的描述，但这已经不是框架可以帮助你做的了^_^。另外即使你的测试失败了，你甚至可以不通过错误提示中的源码文件名和行号信息也可以快速定位到错误的位置所在。因为错误周围是有足够的上下文信息的。</p>
<p>其次，CBehave支持mock。CBehave中mock的实现完全参考了我之前设计的单元测试框架<a href="http://code.google.com/p/lcut" target="_blank">LCUT</a>，下面是一个简单的例子(片断，完整代码参加cbehave/src/example/product_database_test.c)：</p>
<p>FEATURE(1, &quot;Get the total count of employees&quot;)<br />
	&nbsp;&nbsp;&nbsp; SCENARIO(&quot;Get the total count of employees&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GIVEN(&quot;The db connection is ready and there are 5 employees in total&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CBEHAVE_RETV_RETURN(connect_to_database, 0&#215;1234);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CBEHAVE_ARG_RETURN(table_row_count, 5);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CBEHAVE_RETV_RETURN(table_row_count, 0);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GIVEN_END</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WHEN(&quot;We call function: get_total_count_of_employee&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int count = get_total_count_of_employee();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WHEN_END</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; THEN(&quot;The total count of employees we read from db should be 5&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SHOULD_INT_EQUAL(count, 5);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; THEN_END</p>
<p>&nbsp;&nbsp;&nbsp; SCENARIO_END<br />
	FEATURE_END</p>
<p>最后，CBeahve还支持多种SHOULD_XX宏，并且可根据需要灵活添加。目前已支持整型、字符串以及布尔类型的判定。</p>
<p>关于CBehave的实现历程这里也简单说一下。</p>
<p>首先需要确定CBehave的&quot;长相&quot;，也就是CBehave采用的行为描述模板是啥样子的。这块儿确是花了我不少的时间，查看各种资料以及研究其他框架的设计，最终选择了Feature+Scenario以及用Given-When-Then来描述行为的&quot;文档模板&quot;。</p>
<p>其次，有了&quot;文档模板&quot;后如何将其转换为可执行的代码实体？这块也费了我不少脑细胞。思前想后，最终设计是将Feature转换成一个可运行的实体-函数。另外我在函数中使用{}来物理划分Scenario，{}可以隔离变量的可见性和作用域，已达到多个Scenarios定义和执行互不干扰的目的。</p>
<p>上面的标准文档结果中的一个FEATURE宏展开后的样子大致是这样的：</p>
<p>static void cbehave_feature_n(void *_state) {<br />
	&nbsp;&nbsp;&nbsp; cbehave_state _old_state;<br />
	&nbsp;&nbsp;&nbsp; cbehave_feature_entry(&#8230;, &#038;_old_state, _state);</p>
<p>&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /* Scenario #1 */<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int _scenario_state = 0; \<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cbehave_scenario_entry(x, _state);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cbehave_scenario_exit(&#038;_scenario_state, _state);<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /* Scenario #2 */</p>
<p>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /* Scenario #n */</p>
<p>&nbsp;&nbsp;&nbsp; }</p>
<p>_feature_over: \<br />
	&nbsp;&nbsp;&nbsp; cbehave_feature_exit(&#8230;);<br />
	}</p>
<p>关于feature函数的命名，我考虑了很长时间，由于从外界输入的信息无法约束，这里我引入了Feature序号，并同时将序号作为Feature函数名的一部分。例如：<br />
	FEATURE(10, &quot;fopen features&quot;)<br />
	展开后的feature函数名就是cbehave_feature_10，这样做对用户也有一定约束，但约束较小，只需CBehave用户保证各个Feature的序号不同即可。</p>
<p>在使用CBehave时，很可能出现另外一个问题:那就是测试代码在GIVEN或WHEN区段中依赖的一些资源申请或其他代码的初始化可能失败或出现异常。遇到这种情况时，用户多选择return或exit。但一旦用户这样做，CBehave就无法统计和输出测试失败情况或统计的不够准确了。为了尽量保证CBehave统计的精确性，CBehave提供了一个宏FEATURE_RETURN供用户使用。FEATURE_RETURN将控制权转移到Feature函数的末尾，也就是上面宏展开末尾的哪个_feature_over跳转标识符处，这样Feature有机会把这一错误情况记录下来。另外还可以保证其他Feature测试的继续运行。这里举个简单例子(完整代码参见cbehave/src/example/text_editor_test.c)：</p>
<p>FEATURE(1, &quot;Text Editor &#8211; Open Exsited File&quot;)<br />
	&nbsp;&nbsp;&nbsp; SCENARIO(&quot;Open an Exsited File and write something to it&quot;)</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GIVEN(&quot;A file named foo.txt&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; FILE *fp = NULL;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char *buf = &quot;Hello Cbehave!&quot;;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GIVEN_END</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WHEN(&quot;we open the file and write something to it&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fp = fopen(&quot;foo.txt&quot;, &quot;r+&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (!fp)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; FEATURE_RETURN(errno);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WHEN_END</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; THEN(&quot;We should see [Hello Cbehave] has been written into foo.txt&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (fp)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fclose(fp);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; THEN_END<br />
	&nbsp;&nbsp;&nbsp; SCENARIO_END<br />
	FEATURE_END</p>
<p>例子中如果fopen打开foo.txt失败，代码中调用了FEATURE_RETURN来应对这一情况，而不是直接调用return或exit。</p>
<p>最后，与LCUT不同，Cbehave会尝试运行完所有Feature的测试，而不是遇到测试错误就停止运行。</p>
<p>因为之前有过LCUT<a href="http://http://tonybai.com/2005/11/08/the-design-and-implementation-of-c-unittest-framework/" target="_blank">单元测试框架的设计和实现</a>经验，这次CBehave框架的设计和实现就相对容易了些。CBehave的设计用了一天时间，上周末两天&quot;百忙&quot;中抽空完成了编码和测试，目前已提供了<a href="http://cbehave.googlecode.com/files/cbehave-0.1.0-beta.zip" target="_blank">cbehave-0.1.0-beta</a>版供下载体验，欢迎大家提出你的宝贵意见和建议^_^，更多关于CBehave使用方面的细节请参考CBehave用户指南(<a href="http://code.google.com/p/cbehave/wiki/CBehave_User_Guide_cn">http://code.google.com/p/cbehave/wiki/CBehave_User_Guide_cn</a>)。</p>
<p>BTW，我并不是一个纯粹的TDDers。我个人认为完全采用TDD或是BDD还是有一定局限的。是否采用这些方式进行开发还要视产品(或项目)的时间、质量、人员能力等诸多制约因素而定。个人推断国内的C程序员多普遍缺乏采用框架进行单元测试的意识，BDD或TDD的推广还是任重道远的。</p>
<p style='text-align:left'>&copy; 2011, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2011/08/15/cbehave-a-bdd-framework-for-c/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>让BuildBot服务于多个项目</title>
		<link>https://tonybai.com/2011/06/07/use-buildbot-serves-serveral-projects-simultaneously/</link>
		<comments>https://tonybai.com/2011/06/07/use-buildbot-serves-serveral-projects-simultaneously/#comments</comments>
		<pubDate>Tue, 07 Jun 2011 05:32:00 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Agile]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[Buildbot]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[CI]]></category>
		<category><![CDATA[CruiseControl]]></category>
		<category><![CDATA[LCUT]]></category>
		<category><![CDATA[Mock]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Subversion]]></category>
		<category><![CDATA[Thoughtworks]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[持续集成]]></category>
		<category><![CDATA[程序员]]></category>

		<guid isPermaLink="false">http://tonybai.com/2011/06/07/%e8%ae%a9buildbot%e6%9c%8d%e5%8a%a1%e4%ba%8e%e5%a4%9a%e4%b8%aa%e9%a1%b9%e7%9b%ae/</guid>
		<description><![CDATA[<p>多数公司不会仅有一个项目，当你为一个项目引入持续集成实践后，其他项目就会接踵而来。这时你会重新考量BuildBot，考虑如何让BuildBot可以服务于多个项目。<br /><br />如果你有足够的主机资源和人力资源，那为每个项目单独搭建一套CI环境是再好不过的了，每个项目都有专人维...</p>]]></description>
			<content:encoded><![CDATA[<p>多数公司不会仅有一个项目，当你为一个项目引入持续集成实践后，其他项目就会接踵而来。这时你会重新考量<a href="http://tonybai.com/2011/05/18/set-up-ci-environment-with-buildbot/" target="_blank">BuildBot</a>，考虑如何让BuildBot可以服务于多个项目。</p>
<p>如果你有足够的主机资源和人力资源，那为每个项目单独搭建一套CI环境是再好不过的了，每个项目都有专人维护CI环境，各个项目的配置互不干扰。不过对于一些公司来说，这显然有些浪费，BuildBot Master的资源消耗是不大的，我们完全可以使用一套BuildBot Master来服务于多个项目，至少BuildBot是可以支持这样做的。</p>
<p>CI环境中，我们首要关注的就是源码库。多个项目可能各自使用单独的源码库，也可能共享一个源码库并通过目录隔离和识别。无论怎样，我们都可以通过BuildBot Master的配置来满足我们的要求。</p>
<p>如果说多个项目共享一个源码库或是一个项目下的多个子系统放在一个源码库中，这时我们在配置change_source时指定一个变更监测器即可，这个监测器的监测范围从源码库的根路径开始。以Subversion源码库为例，我们可以这样来配置：</p>
<p>c['change_source'] = [SVNPoller(&quot;svn://10.0.0.1:3000&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; svnuser=&#039;tony&#039;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; svnpasswd=&#039;tony&#039;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pollinterval=60,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; split_file=change_path_split)]</p>
<p>我们通过change_path_split来拆分变更文件的路径，假设SVN库结构是这样的：<br />
	svn://10.0.0.1:3000<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; foo_proj<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; trunk<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; main/main.c<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; branches<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; tags<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; bar_proj<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; trunk<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; main/main.c<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; branches<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; tags</p>
<p>我们可以这样来实现change_path_split：</p>
<p>def change_path_split(path):<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pieces = path.split(&#039;/&#039;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if pieces[0] == &#8216;foo_proj&#8217; and pieces[1] == &#039;trunk&#039;:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return (&#039;foo_proj/trunk&#039;, &#039;/&#039;.join(pieces[2:]))<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; elif pieces[0] == &#8216;bar_proj&#8217; and pieces[1] == &#039;trunk&#039;:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return (&#039;bar_proj/trunk&#039;, &#039;/&#039;.join(pieces[2:]))<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return None</p>
<p>不同项目下的文件变更，会导致change_path_split返回不同的值，而change_path_split返回值会被用于匹配不同的Scheduler：</p>
<p>c['schedulers'].append(Scheduler(name=&quot;foo-ci-plan&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; branch=&#039;foo_proj/trunk&#039;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; treeStableTimer=5,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; builderNames=["foo-redhat-builder", "foo-x86-solaris-builder"])</p>
<p>c['schedulers'].append(Scheduler(name=&quot;bar-ci-plan&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; branch=&#039;bar_proj/trunk&#039;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; treeStableTimer=5,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; builderNames=["bar-redhat-builder", "bar-x86-solaris-builder"])</p>
<p>上面各个Scheduler的branch属性会与change_path_split返回值元组中的第一个元素匹配，这样foo-ci-plan便是foo_proj的scheduler，而bar-ci-plan则是bar_proj的scheduler。这样某个项目路径下的文件变更只会触发对应的scheduler开始工作，不会出现误触发。</p>
<p>如果多个项目或一个项目的多个模块使用不同的源码库，同理，我们可以为c['change_source']赋予多个SVNPoller，例如：<br />
	c['change_source'] = [SVNPoller(&quot;svn://10.0.0.1:3000&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; svnuser=&#039;tony&#039;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; svnpasswd=&#039;tony&#039;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pollinterval=60,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; split_file=change_path_split)，</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SVNPoller(&quot;svn://10.0.0.1:4000&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; svnuser=&#039;tony&#039;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; svnpasswd=&#039;tony&#039;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pollinterval=60,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; split_file=another_change_path_split)]</p>
<p>与Scheduler的匹配方式也与上述描述一致，这里就不重复说明了。</p>
<p>不同项目的干系人多不相同，那么集成的结果是如何准确地反馈给项目各自对应的干系人呢？以<a href="http://tonybai.com/2011/05/31/solve-the-problem-that-buildbot-can-not-send-mail/" target="_blank">Mail反馈通知</a>为例，我在BuildBot手册中找到了两种方式，一种方式是通过设置Scheduler的owner属性，然后指定MailNotifier的sendToInterestedUsers=True，意图让BuildBot将Mail通知发到owner list中的每个邮件地址，但经测试后发现，这种方式似乎不好用，不知道是否是BuildBot对该功能的实现上存在问题。</p>
<p>另外一种方式则是配置多个MailNotifier。每个MailNotifier中指定对应builder的名称列表，并通过extraRecipients指定这些Builder对应的项目的干系人Mail地址列表，例如：</p>
<p>c['status'].append(mail.MailNotifier(fromaddr=&quot;<a href="mailto:foo-buildbot@buildbot.net">foo-buildbot@buildbot.net</a>&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; extraRecipients=[&quot;<a href="mailto:foo1@buildbot.net">foo1@buildbot.net</a>&quot;, &quot;<a href="mailto:foo2@buildbot.net">foo2@buildbot.net</a>&quot;],<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; builders=['foo-x86-solaris-builder', 'foo-redhat-builder'],<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; useTls=False,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sendToInterestedUsers=False,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; relayhost=&quot;smtp.buildbot.net&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; smtpUser=&#039;tony&#039;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; smtpPassword=&#039;tony&#039;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; smtpPort=25))</p>
<p>c['status'].append(mail.MailNotifier(fromaddr=&quot;<a href="mailto:bar-buildbot@buildbot.net">bar-buildbot@buildbot.net</a>&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; extraRecipients=[&quot;<a href="mailto:bar1@buildbot.net">bar1@buildbot.net</a>&quot;, &quot;<a href="mailto:bar2@buildbot.net">bar2@buildbot.net</a>&quot;],<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; builders=['bar-x86-solaris-builder', 'bar-redhat-builder'],<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; useTls=False,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sendToInterestedUsers=False,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; relayhost=&quot;smtp.buildbot.net&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; smtpUser=&#039;tony&#039;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; smtpPassword=&#039;tony&#039;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; smtpPort=25))</p>
<p>这样foo的builders构建的结果将发到foo1和foo2；而bar的builders构建结果将反馈到bar1和bar2。</p>
<p>多个项目共享一套BuildBot Master有利有弊，其不足之处可能有如下几点：<br />
	1、项目过多时，可能存在潜在的性能问题<br />
	2、Master的配置被多个项目共享，存在潜在的Conflict问题；<br />
	3、另外master.cfg可能size过大，也不利于阅读和维护。</p>
<p style='text-align:left'>&copy; 2011, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2011/06/07/use-buildbot-serves-serveral-projects-simultaneously/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>解决BuildBot构建结果mail无法发送的问题</title>
		<link>https://tonybai.com/2011/05/31/solve-the-problem-that-buildbot-can-not-send-mail/</link>
		<comments>https://tonybai.com/2011/05/31/solve-the-problem-that-buildbot-can-not-send-mail/#comments</comments>
		<pubDate>Tue, 31 May 2011 09:09:00 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Agile]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[Buildbot]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[CI]]></category>
		<category><![CDATA[CruiseControl]]></category>
		<category><![CDATA[LCUT]]></category>
		<category><![CDATA[Mail]]></category>
		<category><![CDATA[Mock]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Stunnel]]></category>
		<category><![CDATA[Subversion]]></category>
		<category><![CDATA[Thoughtworks]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[持续集成]]></category>
		<category><![CDATA[程序员]]></category>

		<guid isPermaLink="false">http://tonybai.com/2011/05/31/%e8%a7%a3%e5%86%b3buildbot%e6%9e%84%e5%bb%ba%e7%bb%93%e6%9e%9cmail%e6%97%a0%e6%b3%95%e5%8f%91%e9%80%81%e7%9a%84%e9%97%ae%e9%a2%98/</guid>
		<description><![CDATA[<p>在&#8220;使用BuildBot搭建持续集成环境&#8221;一文中我曾经说到：公司使用的mail服务器只支持SSL连接，而BuildBot似乎对SSL连接的支持有些问题，导致构建结果mail无法发送&#8220;。BuildBot实际上使用的是Twisted的mail库来发送邮件的，我下载了Twisted的一些mail发...</p>]]></description>
			<content:encoded><![CDATA[<p>在&ldquo;<a href="http://tonybai.com/2011/05/18/set-up-ci-environment-with-buildbot/" target="_blank">使用BuildBot搭建持续集成环境</a>&rdquo;一文中我曾经说到：公司使用的mail服务器只支持SSL连接，而BuildBot似乎对SSL连接的支持有些问题，导致构建结果mail无法发送&ldquo;。BuildBot实际上使用的是Twisted的mail库来发送邮件的，我下载了Twisted的一些mail发送的例子程序，并使用我的公司mail账户配置，但依旧发送失败。看来这个问题与Twisted的实现有关了。</p>
<p>这个问题已经折腾我许久了，难道非得让我去debug Twisted库？还好，今天我想到了另外一种方法：使用<a href="http://www.stunnel.org" target="_blank">stunnel</a>。原理是这样的：通过stunnel将非SSL连接转换为到公司mail服务器的SSL连接，通过stunnel建立的这条转化通道，mail发送的问题就应该可以解决了。想法归想法，实际上能否达到我的目标，还得通过试验验证。</p>
<p>首先我们需要在BuildBot的master服务器上安装stunnel。</p>
<p>在<a href="http://tonybai.com/2011/04/29/feel-experience-after-using-ubuntu-for-one-year/" target="_blank">Ubuntu</a>服务器上安装stunnel很简单，执行sudo apt-get install stunnel即可。不过今天我却遇到了问题，我的Ubuntu服务器版本是9.04，执行install时发现似乎所有源都不可用了。执行了多次还是这样，sudo apt-get update也无法更新了。突然想到也许是9.04的支持年限到了，到网上一查，果不其然：去年10月份Ubuntu 9.04就不在支持范围了。难道没有专门for老旧Ubuntu版本的源可以使用了吗？还好Ubuntu中文论坛上有答案：Ubuntu官方有一个源<a href="http://old-releases.ubuntu.com/ubuntu">http://old-releases.ubuntu.com/ubuntu</a>是专为已经过了支持期限的版本服务的。将/etc/apt/sources.list备份后打开，将下面内容贴到该文件中：</p>
<p>deb <a href="http://old-releases.ubuntu.com/ubuntu">http://old-releases.ubuntu.com/ubuntu</a> jaunty main restricted universe multiverse<br />
	deb <a href="http://old-releases.ubuntu.com/ubuntu">http://old-releases.ubuntu.com/ubuntu</a> jaunty-security main restricted universe multiverse<br />
	deb <a href="http://old-releases.ubuntu.com/ubuntu">http://old-releases.ubuntu.com/ubuntu</a> jaunty-updates main restricted universe multiverse<br />
	deb <a href="http://old-releases.ubuntu.com/ubuntu">http://old-releases.ubuntu.com/ubuntu</a> jaunty-backports main restricted universe multiverse<br />
	deb <a href="http://old-releases.ubuntu.com/ubuntu">http://old-releases.ubuntu.com/ubuntu</a> jaunty-proposed main restricted universe multiverse<br />
	deb-src <a href="http://old-releases.ubuntu.com/ubuntu">http://old-releases.ubuntu.com/ubuntu</a> jaunty main restricted universe multiverse<br />
	deb-src <a href="http://old-releases.ubuntu.com/ubuntu">http://old-releases.ubuntu.com/ubuntu</a> jaunty-security main restricted universe multiverse<br />
	deb-src <a href="http://old-releases.ubuntu.com/ubuntu">http://old-releases.ubuntu.com/ubuntu</a> jaunty-updates main restricted universe multiverse<br />
	deb-src <a href="http://old-releases.ubuntu.com/ubuntu">http://old-releases.ubuntu.com/ubuntu</a> jaunty-backports main restricted universe multiverse<br />
	deb-src <a href="http://old-releases.ubuntu.com/ubuntu">http://old-releases.ubuntu.com/ubuntu</a> jaunty-proposed main restricted universe multiverse</p>
<p>保存后执行update，然后再重新install stunnel，这回一切OK了。</p>
<p>接下来，我们来配置stunnel。</p>
<p>我们先打开/etc/default/stunnel4，将其中的ENABLED配置项的值从0改为1，这样我们就允许stunnel在主机重启后可以被自动启动。</p>
<p>/etc/stunnel/stunnel.conf这个配置文件才是我们需要重点关注的。主要改动的配置项及说明如下：</p>
<p>; Use it for client mode<br />
	client = yes ; 我们使用的是stunnel的client模式，所以这里将no改为yes</p>
<p>; cert = /etc/stunnel/mail.pem ; 注释掉该行</p>
<p>; 打开debug模式以及log文件输出，便于我们在使用初期问题的查找<br />
	debug = 7<br />
	output = /var/log/stunnel4/stunnel.log</p>
<p>; 下面是关键配置，stunnel将接收本地到25端口的mail连接，并将该mail连接转换为到smtp.your_domain.com:465的SSL连接<br />
	[ssmtp]<br />
	accept&nbsp; = 127.0.0.1:25<br />
	connect = smtp.your_domain.com:465</p>
<p>配置就是这些了。我们通过sudo /etc/init.d/stunnel4 start启动stunnel。如果你在启动时遇到问题，别忘了查看一下/var/log/stunnel4/stunnel.log中的内容，多数情况下你都会很快的发现问题所在。比如我第一次启动stunnel时就得到了如下错误信息：<br />
	[Failed: /etc/stunnel/stunnel.conf]<br />
	You should check that you have specified the pid= in you configuration file</p>
<p>这个错误信息可能会让你误以为是配置出现了错误，但通过查看log会发现，原来错误原因是25端口已经被占用了。占用25端口的程序是sendmail，停掉sendmail服务，再次启动stunnel，我们得到以下的成功信息：</p>
<p>Starting SSL tunnels: [Started: /etc/stunnel/stunnel.conf] stunnel.</p>
<p>最后，我们来测试一下stunnel是否可以真正解决我们的问题。修改BuildBot master的master.cfg中的mail发送配置：</p>
<p>c['status'].append(mail.MailNotifier(fromaddr=&quot;SENDER_MAIL_ADDR&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; extraRecipients=["RECIPIENT_MAIL_ADDR"],<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sendToInterestedUsers=False,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; useTls=False,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; relayhost=&quot;127.0.0.1&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; smtpUser=&#039;foo&#039;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; smtpPassword=&#039;foo!&#039;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; smtpPort=25))</p>
<p>有了stunnel，我们就可以使用非SSL连接来发送mail了，不过我们的buildbot连的是stunnel监听的本机25端口。</p>
<p>触发一次构建，稍等片刻，我的Thunderbird里就出现了BuildBot构建失败的提醒mail，我们成功了。</p>
<p style='text-align:left'>&copy; 2011, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2011/05/31/solve-the-problem-that-buildbot-can-not-send-mail/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>使用BuildBot搭建持续集成环境</title>
		<link>https://tonybai.com/2011/05/18/set-up-ci-environment-with-buildbot/</link>
		<comments>https://tonybai.com/2011/05/18/set-up-ci-environment-with-buildbot/#comments</comments>
		<pubDate>Wed, 18 May 2011 15:32:00 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Agile]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[Buildbot]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[CI]]></category>
		<category><![CDATA[CruiseControl]]></category>
		<category><![CDATA[LCUT]]></category>
		<category><![CDATA[Mock]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Subversion]]></category>
		<category><![CDATA[Thoughtworks]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[持续集成]]></category>
		<category><![CDATA[程序员]]></category>

		<guid isPermaLink="false">http://tonybai.com/2011/05/18/%e4%bd%bf%e7%94%a8buildbot%e6%90%ad%e5%bb%ba%e6%8c%81%e7%bb%ad%e9%9b%86%e6%88%90%e7%8e%af%e5%a2%83/</guid>
		<description><![CDATA[<p>部门的持续集成<br />
一直做的不太好，我们开发部这边甚至一直没能做起来，这其中有各种原因：工具、意识、执行力、沟通等等。将持续集成引入到我们的开发过程中也一直是我的一个目标。去年末启动的一个项目让我感到时机变得成熟了。<br />
<br />
新项目的代码是完全重写的，这样的机...</p>]]></description>
			<content:encoded><![CDATA[<p>部门的<a href="http://book.douban.com/subject/2580604/" target="_blank">持续集成</a>一直做的不太好，我们开发部这边甚至一直没能做起来，这其中有各种原因：工具、意识、执行力、沟通等等。将持续集成引入到我们的开发过程中也一直是我的一个目标。去年末启动的一个项目让我感到时机变得成熟了。</p>
<p>新项目的代码是完全重写的，这样的机会甚是难得。因为大多数情况下大家都是在维护现有系统：做些添添补补、修正Bug以及优化之类的事情。项目初期，我特别向大家强调了要严格遵守统一代码风格并将<a href="http://http://tonybai.com/2010/07/29/use-astyle-to-beautify-your-code/" target="_blank">astyle</a>代码格式化工具介绍给大家，手把手地教大家如何利用类似<a href="http://tonybai.com/2010/09/30/opensource-a-lightweight-c-unit-test-framework/" target="_blank">LCUT</a>这样的<a href="http://tonybai.com/2005/11/08/the-design-and-implementation-of-c-unittest-framework/" target="_blank">单元测试框架</a>编写单元测试，讲解什么是<a href="http://tonybai.com/2008/04/12/mock-test-in-c-unit-test/" target="_blank">Mock测试</a>。前些时间我又将<a href="http://tonybai.com/2011/04/21/apply-style-check-to-c-code/" target="_blank">代码风格检查</a><br />
	脚本加入到工程的构建过程中，并将代码风格检查作为最终构建目标的关键依赖，强制大家编写出统一风格的代码。</p>
<p>情况就是这样的情况，的确我们现在只做到了这些。不过有了这些基础，我就更有信心去做持续集成了。</p>
<p>今年年初部门统一部署了产品的多平台移植的开发任务，作为新项目，我们的成果物被要求天生就应具备适合在多个平台上运行的能力。这次产品平台移植仿佛一针催化剂加快了我在项目中实施持续集成的脚步。我希望搭建出这样一套系统：每当开发人员提交代码后，持续集成框架都能发现这些代码变动，并在多个不同平台的主机上分别Checkout出最新代码，Build，Check代码风格并运行单元测试集，最终将结果通知所有人。</p>
<p>我需要找到满足我这一需求的工具。记得若干年前我第一次研究持续集成时曾经研究过一款名为<a href="http://tonybai.com/2008/08/20/the-experience-of-cruisecontrol-rb/" target="_blank">CruiseControl.rb</a>的工具，不过很遗憾的是这款工具似乎早已不更新了。在<a href="http://cruisecontrolrb.thoughtworks.com" target="_blank">Thoughtworks的官方站点</a>上，其最后一次发布是在2年前了。另外CruiseControl.rb一个比较大的缺憾就是性能差些。另外如果想满足我在多个不同平台主机上同时运行构建以及测试的要求，似乎需要部署多套CruiseControl.rb。</p>
<p>在寻找工具的路上，我发现了<a href="http://trac.buildbot.net/" target="_blank">BuildBot</a>。这是一款由Python实现的开源持续集成工具。与CruiseControl.rb相比，其性能更好，其Master+Slaves的结构更易于扩展，并且可以很好地满足多平台版本构建的需求，大名鼎鼎的Google Chrome浏览器项目用的也是这款工具。另外我个人对Python的更加青睐也让我决定使用这款工具。</p>
<p>BuildBot的文档比较丰富和全面，这也使其安装过程比较简单。BuildBot由两部分组成，一部分是Master，用于监视代码库变动，控制各个Slave节点进行构建操作，并收集反馈结果以各种方式（Mail、Html等）展示给用户；另外一部分就是Slave了。每个Slave节点都承担着构建过程的具体工作：他们接收Master发过来的指令，并按指令一步一步完成构建工作，并将结果反馈给Master。</p>
<p>一个BuildBot持续集成环境就是由一个Master与一些Slaves组成的。其安装过程大致如下：<br />
	1、在装有Python（最好是Python 2.6.x版本）的Master主机上安装Buildbot master：下载BuildBot安装包（我用的是最新的<a href="http://buildbot.googlecode.com/files/buildbot-0.8.3p1.zip" target="_blank">BuildBot-0.8.3p1</a>）。解包后，执行python setup.py build和python setup.py install安装BuildBot master包。注意install默认是需要root权限的。<br />
	2、安装BuildBot依赖包：下载最新的Twisted包（我用的是11.0.0）与zope.interface包（我用的是3.6.1），安装方法与BuildBot一致。<br />
	3、在装有Python（最好是Python 2.6.x版本）的各个Slave主机上安装BuildBot slave：下载<a href="http://buildbot.googlecode.com/files/buildbot-slave-0.8.3.zip" target="_blank">Buildslave</a>安装包、最新的Twisted包与zope.interface包，安装方法与BuildBot Master一致。<br />
	4、以上安装完成后，在Master host上执行buildbot，在各Slave host上运行buildslave，检查一下是否成功安装了。</p>
<p>安装Ok，我们就可以建立Master实例以及诸多Slave的实例了。先说说Master。在Master主机上某路径下，创建foo_ci_master目录，进入foo_ci_master目录下，执行：&quot;buildbot create-master ./&quot;。执行后，在当前目录下会有master.cfg.sample文件。这是一个样板文件，我们将其改名为master.cfg后，打开master.cfg，开始进行master的配置。</p>
<p>Master的master.cfg是这套持续集成系统的核心。我们用一个简单的例子来说明这个配置。假设我们的持续集成环境由三台主机组成：Master Host（假设其ip为10.0.0.1）以及两台Slave Host，其中一台Slave Host上运行着RHEL 5.5，而另外一台Slave Host上则运行着PC Solaris 10。我们希望当有代码被提交到代码库中后，Master可及时发现这一变化，并且指挥两台Slave Host检出最新代码并且都能Build成功。</p>
<p>下面是master.cfg中的一些关键配置及说明(省略了一些默认配置)：</p>
<p>#<br />
	# master.cfg<br />
	#</p>
<p>c['slaves'] = [BuildSlave(&quot;x86-solaris-bot&quot;, &quot;x86-solaris-bot-passwd&quot;),<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; BuildSlave(&quot;redhat-bot&quot;, &quot;redhat-bot-passwd&quot;)]</p>
<p>这里告诉Master我们有两个Slave node，分别是x86-solaris-bot和redhat-bot，而这两个Slave登录Master的密码分别为x86-solaris-bot-passwd和redhat-bot-passwd。</p>
<p>我们使用<a href="http://tonybai.com/2011/03/23/also-talk-about-solving-the-svn-conflicts/" target="_blank">subversion</a>作为我们源码版本管理工具，所以我们采用SVNPoller来监测源码库的变化：</p>
<p>from buildbot.changes.svnpoller import SVNPoller<br />
	c['change_source'] = SVNPoller(&quot;svn://10.10.0.1:8888&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; svnuser=&#039;YOUR_SVN_USER&#039;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; svnpasswd=&#039;YOUR_SVN_PASSWD&#039;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pollinterval=30,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; split_file=foo_split_file)</p>
<p>def foo_split_file(path):<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pieces = path.split(&#039;/&#039;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if pieces[0] == &#8216;foo&#8217; and pieces[1] == &#039;trunk&#039;:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return (&#039;foo/trunk&#039;, &#039;/&#039;.join(pieces[2:]))<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return None</p>
<p>在SVNPoller的参数中split_file是比较难理解的一个。它是为下面的Scheduler提供服务的。split_file会将变更的源码文件的完整路径信息进行拆分，并返回一个(branch, relative_pathname)的元组。而Scheduler将尝试匹配元组中的branch以决定此次变更是否是自己所关心的。看下面配置代码：</p>
<p>c['schedulers'].append(Scheduler(name=&quot;foo-ci-plan&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; branch=&#039;foo/trunk&#039;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; treeStableTimer=5,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; builderNames=["foo-redhat-builder", "foo-x86-solaris-builder"]))</p>
<p>显然这个Scheduler关心&quot;foo/trunk&quot;这个branch。一旦某源码文件归属于该分支（如svn://10.10.0.1:8888/foo/trunk/main/main.c），则该Scheduler会启动构建过程。其构建过程将通过两个builder完成，它们分别是foo-redhat-builder和foo-x86-solaris-builder。这样一来，我们就可以适当定义foo_split_file并设置多个Scheduler，以满足我们对不同branch的不同构建需要。</p>
<p>builder都是关联到某个builder factory的，而下面则是factory的配置：</p>
<p>foo_builder_factory = factory.BuildFactory()<br />
	foo_builder_factory.addStep(SVN(mode=&#039;update&#039;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; baseURL=&#039;svn://10.10.0.1:8888/&#039;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; defaultBranch=&#039;foo/trunk&#039;))<br />
	foo_builder_factory.addStep(Compile(command=["make"]))</p>
<p>这个factory生产出来的builder会执行两个step：首先执行svn update，将svn://10.10.0.1:8888/foo/trunk更新到本地；然后执行make命令。</p>
<p>下面是builder的设置：</p>
<p>b1 = {&#039;name&#039;: &quot;foo-redhat-builder&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#039;slavename&#039;: &quot;redhat-bot&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#039;builddir&#039;: &quot;foo-redhat&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#039;factory&#039;: foo_builder_factory,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</p>
<p>b2 = {&#039;name&#039;: &quot;foo-x86-solaris-builder&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#039;slavename&#039;: &quot;x86-solaris-bot&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#039;builddir&#039;: &quot;foo-x86-solaris&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#039;factory&#039;: foo_builder_factory,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</p>
<p>c['builders'] = [b1, b2]</p>
<p>builder的设置看起来没那么难，一目了然。</p>
<p>BuildBot Slave的创建和配置就更加简单了。首先到那台运行着solaris系统的Slave host上，在适当目录下创建foo_ci_slave目录，进入该目录后，执行&ldquo;buildslave create-slave &#8211;umask=022 ./ 10.0.0.1:9989 x86-solaris-bot x86-solaris-bot-passwd&rdquo;命令，一个Slave就创建完了，实际上也配置完了，无需额外配置了。其配置文件就是foo_ci_slave下面的buildbot.tac文件。Rhel上的slave也是如此创建的。</p>
<p>启动Master。在Master host的foo_ci_master下面，执行buildbot start ./即可启动buildbot master，其当前日志会被输出到twistd.log中；如果要停止buildbot master，依旧是在该目录下，但执行buildbot stop ./。</p>
<p>启动slave。在Slave Host的foo_ci_slave下面，执行buildslave start ./即可启动buildbot slave，其当前日志会被输出到twistd.log中；如果要停止buildbot slave，依旧是在该目录下，但执行buildslave stop ./。</p>
<p>当Master和各个Slave都成功启动后，我们就可以来试试执行一次Build过程：修改foo/trunk下的某源码文件并提交。Master将会侦听到变更，便会启动两个Slave Host上的build过程。此次构建的结果在哪里可以看到呢？试试访问http://10.0.0.1:8010，页面上红色代表构建失败，绿色代表构建成功。</p>
<p>Buildbot还支持将构建结果通过Mail通知的机制，不过由于公司用的是ssl方式，我试验了许久都没能将mail发出来。不知道是不是Twisted的Mail包的实现有问题还是其他什么原因，后续会继续查证。</p>
<p style='text-align:left'>&copy; 2011, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2011/05/18/set-up-ci-environment-with-buildbot/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>lcut增加对mock的支持</title>
		<link>https://tonybai.com/2010/10/29/lcut-add-mock-support/</link>
		<comments>https://tonybai.com/2010/10/29/lcut-add-mock-support/#comments</comments>
		<pubDate>Fri, 29 Oct 2010 14:47:00 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[autoconf]]></category>
		<category><![CDATA[automake]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[Cmockery]]></category>
		<category><![CDATA[Configure]]></category>
		<category><![CDATA[GNU]]></category>
		<category><![CDATA[LCUT]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Makefile]]></category>
		<category><![CDATA[Mock]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[Unittest]]></category>
		<category><![CDATA[单元测试]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[程序员]]></category>

		<guid isPermaLink="false">http://tonybai.com/2010/10/29/lcut%e5%a2%9e%e5%8a%a0%e5%af%b9mock%e7%9a%84%e6%94%af%e6%8c%81/</guid>
		<description><![CDATA[<p>记得恰好是在一个月前的今天，我发布了lcut<br />
(轻量级C语言单元测试框架)0.1.0版本<br />
。由于发布仓促，文档没能及时跟上。在stackoverflow<br />
的一个关于单元测试的帖子<br />
上，一位叫Craig McQueen<br />
的朋友也给出了建议：&#34;Some documentation would be helpful. Project b...</p>]]></description>
			<content:encoded><![CDATA[<p>记得恰好是在一个月前的今天，我<a href="http://tonybai.com/2010/09/30/opensource-a-lightweight-c-unit-test-framework/" target="_blank">发布了lcut</a>(轻量级<a href="http://tonybai.com/2005/11/08/the-design-and-implementation-of-c-unittest-framework/" target="_blank">C语言单元测试框架</a>)<a href="http://code.google.com/p/lcut/" target="_blank">0.1.0版本</a><br />
	。由于发布仓促，文档没能及时跟上。在<a href="http://stackoverflow.com" target="_blank">stackoverflow</a>的一个<a href="http://stackoverflow.com/questions/65820/unit-testing-c-code" target="_blank">关于单元测试的帖子</a><br />
	上，一位叫<a href="http://craig.mcqueen.id.au/" target="_blank">Craig McQueen</a>的朋友也给出了建议：&quot;Some documentation would be helpful. Project background and goals, a features list, advantages over existing alternatives, etc would be helpful for people who are checking it out for the first time.&quot; 看完这个建议后心里那个汗啊！不过一想到用E文编写文档心里就有些打怵。就这样在这一个月里文档依旧没有改观:(。不过，lcut本身还是有一些进步的，这两天一直规划着为lcut增加<a href="http://tonybai.com/2008/04/12/mock-test-in-c-unit-test/" target="_blank">mock</a>的支持，今天终于将这个功能加进了lcut，并发布了<a href="http://code.google.com/p/lcut/" target="_blank">lcut-0.2.0-beta</a>版，欢迎大家试用，并提出意见和建议。</p>
<p>之前在单元测试过程中使用<a href="http://tonybai.com/2009/08/22/introduce-cmockery-for-c-unit-test/" target="_blank">cmockery</a>中提供的mock功能，cmockery也是lcut的mock功能的直接灵感来源。与cmockery不同的是lcut将对输出参数的mock和对函数返回值的mock区分开来，这样用起来更加直观。</p>
<p>这里用一个简单的例子(完整代码在lcut包product_database_test.c文件中)来说明一下lcut的mock功能如何使用：</p>
<p>/* product_database.c */<br />
	int get_total_count_of_employee() {<br />
	&nbsp;&nbsp;&nbsp; database_conn *conn = NULL;<br />
	&nbsp;&nbsp;&nbsp; int retv = -1;<br />
	&nbsp;&nbsp;&nbsp; int total_count = -1;</p>
<p>&nbsp;&nbsp;&nbsp; conn = connect_to_database(&quot;tonybai&quot;, &quot;tonybai&quot;, &quot;mysql&quot;);<br />
	&nbsp;&nbsp;&nbsp; if (!conn)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -1;</p>
<p>&nbsp;&nbsp;&nbsp; retv = table_row_count(conn, &quot;EMPLOYEE_TABLE&quot;, &#038;total_count);<br />
	&nbsp;&nbsp;&nbsp; if (retv &lt; 0)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -1;<br />
	&nbsp;&nbsp;&nbsp; return total_count;<br />
	}</p>
<p>/* product_database_test.c */<br />
	database_conn *connect_to_database(const char *user,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const char *passwd,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const char *serviceid) {<br />
	&nbsp;&nbsp;&nbsp; return (database_conn*)LCUT_MOCK_RETV();<br />
	}</p>
<p>int table_row_count(const database_conn *conn,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const char *table_name,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int *total_count) {<br />
	&nbsp;&nbsp;&nbsp; (*total_count) = (int)LCUT_MOCK_ARG();<br />
	&nbsp;&nbsp;&nbsp; return (int)LCUT_MOCK_RETV();<br />
	}</p>
<p>void tc_get_total_count_of_employee_ok(lcut_tc_t *tc, void *data) {<br />
	&nbsp;&nbsp;&nbsp; LCUT_RETV_RETURN(connect_to_database, 0&#215;1234);<br />
	&nbsp;&nbsp;&nbsp; LCUT_ARG_RETURN(table_row_count, 5);<br />
	&nbsp;&nbsp;&nbsp; LCUT_RETV_RETURN(table_row_count, 0);</p>
<p>&nbsp;&nbsp;&nbsp; LCUT_INT_EQUAL(tc, 5, get_total_count_of_employee());<br />
	}</p>
<p>被mock的函数多为系统API或执行代价较高的第三方库函数，我们在业务代码更关心的是这些函数的接口行为，而C语言中函数的接口行为表现为：返回值和输出参数。我们需要通过控制被mock函数的接口行为来达到测试我们业务代码的目的，所以我们需要mock这些函数的返回值和输出参数。上面例子中connect_to_database和table_row_count就是两个被mock了的库函数。我们通过LCUT_MOCK_RETV来mock函数的返回值，通过LCUT_MOCK_ARG来mock函数的输出参数。在测试代码tc_get_total_count_of_employee_ok中，我们分别通过LCUT_RETV_RETURN和LCUT_ARG_RETURN来控制前面两个被mock的函数中mock obj的返回值和输出参数: LCUT_RETV_RETURN(connect_to_database, 0&#215;1234)告诉connect_to_database返回(int)0&#215;1234，相应的，LCUT_ARG_RETURN(table_row_count, 5)则告诉table_row_count执行后其输出参数*total_count的值为5。有了这些设定的mock obj我们就可以专注于我们业务层代码的白盒逻辑单元测试了，一旦connect_to_database和table_row_count的外部行为被控制后，业务层的代码get_total_count_of_employee的行为也就是可预期的了，我们用断言测试即可。</p>
<p>由于实现原理限制，如果你的函数输出参数类型或返回值类型为double*/float*，那么这个函数还不能使用lcut的mock功能，否则会编译出错。但绝大多数软件开发领域都很少使用浮点计算，所以lcut的mock还是可以满足大多数需要的。</p>
<p>题外话：<br />
	在公司使用代理上网，svn无法直接访问google code，这个问题一直困扰着我，直到今天才知道可以为svn客户端设置代理，设置步骤如下：<br />
	-&gt; vi ~/.subversion/servers<br />
	-&gt; 增加如下设置：<br />
	&nbsp;&nbsp; [global]<br />
	&nbsp;&nbsp; http-proxy-host = 你的代理主机域名或ip<br />
	&nbsp;&nbsp; http-proxy-port = 端口<br />
	&nbsp;&nbsp; http-proxy-username = 你的用户名<br />
	&nbsp;&nbsp; http-proxy-password = 你的密码<br />
	设置后，svn立马就可以连上google code的svn server了。</p>
<p style='text-align:left'>&copy; 2010, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2010/10/29/lcut-add-mock-support/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>由bool类型引发的一个问题</title>
		<link>https://tonybai.com/2010/10/21/a-problem-caused-by-bool-type/</link>
		<comments>https://tonybai.com/2010/10/21/a-problem-caused-by-bool-type/#comments</comments>
		<pubDate>Thu, 21 Oct 2010 15:28:00 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[Bool]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[C99]]></category>
		<category><![CDATA[LCUT]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[内存]]></category>
		<category><![CDATA[布尔类型]]></category>
		<category><![CDATA[标准C]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[编译器]]></category>

		<guid isPermaLink="false">http://tonybai.com/2010/10/21/%e7%94%b1bool%e7%b1%bb%e5%9e%8b%e5%bc%95%e5%8f%91%e7%9a%84%e4%b8%80%e4%b8%aa%e9%97%ae%e9%a2%98/</guid>
		<description><![CDATA[<p>C99<br />原生支持布尔类型，类型名字为_Bool。对C程序员来说，这个名字有些&#8220;不伦不类&#8221;，还好一般C标准库<br />实现的头文件中都用宏bool来替代_Bool。C99虽说是C语言当前的最新标准，但是它也有10年历史之久了。据说C1x标准<br />正在讨论制定中，有兴趣的朋友可以到标...</p>]]></description>
			<content:encoded><![CDATA[<p><a href="http://en.wikipedia.org/wiki/C99" target="_blank">C99</a> 原生支持布尔类型，类型名字为_Bool。对C程序员来说，这个名字有些&ldquo;不伦不类&rdquo;，还好一般C标准库 实现的头文件中都用宏bool来替代_Bool。C99虽说是C语言当前的最新标准，但是它也有10年历史之久了。据说<a href="http://en.wikipedia.org/wiki/C1X" target="_blank">C1x标准</a> 正在讨论制定中，有兴趣的朋友可以到<a href="http://www.open-std.org/jtc1/sc22/wg14/" target="_blank">标准C工作组</a> 官方站点上去瞧瞧。</p>
<p>有些跑题了^_^！其实这篇文章想说的不是C1x标准，而是一个与布尔类型有关的问题的分析解决过程。</p>
<p>上周为项目的复用库增加了一个小功能，对外表现形式就是一组函数。使用<a href="http://tonybai.com/2010/09/30/opensource-a-lightweight-c-unit-test-framework/" target="_blank">lcut</a> 对这组函数进行了详尽的<a href="http://tonybai.com/2005/11/08/the-design-and-implementation-of-c-unittest-framework/" target="_blank">单元测试</a> ，所有用例都顺利通过。今天和一位同事交流后，觉得应该对这个功能作些改动，针对一些异常情况作些完善。修改方案很简单，就是在一个外部可见的结构体里增加一个表示当前状态的布尔类型的字段，然后在各个函数接口中设置该字段的值并根据该字段的值做相应的处理。</p>
<p>按照既定的思路修改后，原先的用例依旧可以全部pass。继续修改单元测试代码，增加针对此次改动的用例。编译并运行测试，这次则没有那么幸运-有几个用例失败了。查看失败原因，确有一两个是因为逻辑上的问题导致。</p>
<p>修正后，继续运行测试，依旧有两个用例无法通过。 仔细查看了一遍库代码以及单元测试代码，没有发现明显的错误。将LCUT_TRUE断言换成LCUT_INT_EQUAL断言，重新运行测试，发现期望值为true的断言，实际值却是一个-3146789这样的大数。看到这种情况我的第一反应是：是不是内存被污染了？比如代码里有内存覆盖或Buffer溢出的情况。又仔细浏览了一遍代码，依旧没有发现蛛丝马迹。采用<a href="http://tonybai.com/2006/01/08/debug-multiple-process-program-using-gdb/" target="_blank">gdb</a> 单步执行测试程序，无奈lcut采用了许多回调函数，导致在gdb中无法追踪到我期望的符号。未果后，我尝试换成最原始的增加打印日志输出的调试方式，终于发现了问题端倪。</p>
<p>具体是这样的：在库代码和测试用例代码中，我都输出了bool类型的size，但结果却大相径庭。库代码中输出的sizeof(bool)等于1，而在测试程序中输出的值却为4，这个长度差异直接导致了前面的-3146789的出现。</p>
<p>这里我要补充一下，C99的布尔类型(bool)在stdbool.h中定义：#define bool _Bool(注: _Bool是原生的)，这只有在C99下才生效。考虑到有些编译器不支持C99或默认语言标准不是C99，为了兼容，自定义了一份bool的定义，并通过预编译宏与标准定义隔开：</p>
<p>#if __STDC_VERSION__ &gt;= 199901L<br />
	#include<br />
	#else<br />
	#undef&nbsp; bool<br />
	#undef&nbsp; true<br />
	#undef&nbsp; false<br />
	typedef enum {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; false,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; true<br />
	} bool;<br />
	#endif /* __STDC_VERSION__ &gt;= 199901L */</p>
<p>难道原因是库中代码采用了标准bool类型，而测试代码中采用的是自定义的bool类型？似乎没道理啊。突然间看到了屏幕上编译测试代码的gcc命令行输出：<br />
	gcc std=c99 -c -o testall.o testall.c &#8230; (后面还有较长的链接库的参数，这里省略）<br />
	gcc -o testall testall.c &#8230;</p>
<p>怎么对testall.c编译了两次呢？测试代码目录下的Makefile并没有包含第一个gcc命令啊，又翻了翻顶层目录下的Makefile，我找到了答案：原来顶层目录下的Makefile中采用了如下脚本：<br />
	OBJ = $(SRC:.c=.o)<br />
	${OBJ}: ${SRC}<br />
	&nbsp;&nbsp;&nbsp; ${CC} ${CFLAGS} -c ${SRC}<br />
	脚本根据.c文件替换获得.o文件名，并在同名.o和.c间建立依赖，这样所有.c文件都会被先编译为.o文件。</p>
<p>不过第一次编译的结果显然做了无用功，因为第二行命令执行后会覆盖第一行命令生成的.o文件。但恰恰第二行gcc命令中没有加入-std=c99的编译选项，导致testall.c这个编译单元中的bool使用了自定义的bool，导致了其长度为4个字节。</p>
<p>真相终于大白，就是因为testall.c所在目录下的Makefile编写时忘记添加-std=c99选项才导致了上述的问题。又检查了一下其他的存放单元测试代码的目录，发现所有Makefile都存在此问题。以前在没有使用bool类型时这样的Makefile是不会有问题的，关键就是这次我们用了bool类型，问题才暴露出来。</p>
<p>使用C语言，你就不得不常常与指针内存问题、编译器或<a href="http://tonybai.com/2007/12/08/those-things-about-symbol-linkage/" target="_blank">链接器</a> 问题做斗争，其中的痛苦你最清楚，但处理这些事的过程中所蕴含的快乐也只有你自己最能体会到。继续痛并快乐着吧！</p>
<p style='text-align:left'>&copy; 2010, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2010/10/21/a-problem-caused-by-bool-type/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
