月度归档:2011年10月

chromium中的单元测试

对于chromium这样庞大的系统来说,单元测试是少不了的。chromium代码库中存在大量的单元测试代码,通常命名为xxx_unittest.cc。chromium中采用的单元测试框架为gtest。

gtest是google开发的一套开源的C++测试框架,支持多种平台(Linux, Mac OS X, Windows, Cygwin, WindowsCE和Symbian)。它支持自动的测试发现,丰富的断言及用户自定义断言,死亡测试,致命和非致命的失败,参数化类型和值的测试,XML测试报告产生。gtest项目地址为http://code.google.com/p/googletest。

1. gtest简单示例

闲话少说,我们先用一个简单的示例来感受一下gtest的基本功能。首先下载gtest压缩包,ubuntu下很简单,解压源代码后:

./configure

make

即可。下面写一段简单的代码:

// foo_unittest.cc

#include "gtest/gtest.h"

namespace {

class FooTest : public testing::Test {
protected:
  FooTest() {
    // setup work for each test.
  }

  virtual ~FooTest() {
    // clean-up work
  }

  virtual void SetUp() {
    // Code here will be called immediately after the constructor
    // (right before each test).
  }

  virtual void TearDown() {
    // Code here will be called immediately aftr each test
    // (right before the destructor).
  }
};

TEST_F(FooTest, ZeroEqual) {
  EXPECT_EQ(0, 0);
}

TEST_F(FooTest, OneEqual) {
  EXPECT_EQ(1, 1);
}

} // namespace

接下来编译链接程序:

g++ -o foo_unittest.o -c foo_unittest.cc –I/home/work/dev-app/gtest-1.6.0/include

libtool -mode=link gcc –g –o foo_unittest foo_unittest.o /home/work/dev-app/gtest-1.6.0/lib/libgtest_main.la –lm

gtest提供了libgtest_main.la,我们不需要编写main文件就可以生成可执行程序。执行foo_unittest的输出如下:

2. chromium中的测试基类

本文并不探讨gtest的用法,如果希望详细了解gtest的使用,建议阅读gtest文档,另外玩转Google开源C++单元测试框架Google Test系列也写得非常不错。下面看看chromium中的单元测试。

chromium包含了一个基础测试套件类TestSuite,代码位于src/base/test/test_suite.h。它定义了一个基本的测试套件框架,用于运行基于gtest的测试用例。在main程序中示例化该类并运行类的Run方法,即可运行链接到可执行程序的任何gtest用例。在src/base/test/下有一个测试程序,主程序非常简单:

#include “base/test/test_suite.h”

int main(int argc, char** argv) {

  return base::TestSuite(argc, argv).Run();

}

至于执行哪些测试用例,取决于链接了哪些gtest测试代码。

理解chromium构建系统

chromium采用了GYP(Generate Your Projects)构建系统。关于GYP的资料不多,google code上有该开源项目。GYP是google为chromium的构建而开发的一套构建系统,设计的初衷是用来产生原生的IDE工程文件(Visual Studio, Xcode)来编译chromium。

典型的gyp文件如下:

{
  ‘variables’: {
    .
    .
    .
  },
  ‘includes’: [
    ‘../build/common.gypi’,
  ],
  ‘target_defaults’: {
    .
    .
    .
  },
  ‘targets’: [
    {
      ‘target_name’: ‘target_1′,
        .
        .
        .
    },
    {
      ‘target_name’: ‘target_2′,
        .
        .
        .
    },
  ],
  ‘conditions’: [
    [‘OS=="linux"’, {
      ‘targets’: [
        {
          ‘target_name’: ‘linux_target_3′,
            .
            .
            .
        },
      ],
    }],
    [‘OS=="win"’, {
      ‘targets’: [
        {
          ‘target_name’: ‘windows_target_4′,
            .
            .
            .
        },
      ],
    }, { # OS != "win"
      ‘targets’: [
        {
          ‘target_name’: ‘non_windows_target_5′,
            .
            .
            .
        },
    }],
  ],
}

其中:

‘variables’: 变量定义,可用于文件的其它部分

‘includes’: 包含其它文件的清单,被包含的文件后缀通常为.gypi

‘target_defaults’: 可应用于所有目标的设置

‘targets’: 目标清单,每个目标是一个字典(dictionary),包含构建该目标所需的一些信息

‘conditions’: 条件说明,可以针对平台或者变量定义设置

一个典型的可执行构建目标(executable target)的gyp文件

{
  ‘targets’: [
    {
      ‘target_name’: ‘foo’,
      ‘type’: ‘executable’
      ‘msvs_guid’: ’5ECEC9E5-8F23-47B6-93E0-C3B328B3BE65′,
      ‘dependencies’: [
        ‘xyzzy’,
        ‘../bar/bar.gyp:bar’,
      ],
      ‘defines’: [
        ‘DEFINE_FOO’,
        ‘DEFINE_A_VALUE=value’,
      ],
      ‘include_dirs’: [
        ‘..’,
      ],
      ‘sources’: [
        ‘file1.cc’,
        ‘file2.cc’,
      ],
      ‘conditions’: [
        [‘OS=="linux"’, {
          ‘defines’: [
            ‘LINUX_DEFINE’,
          ],
          ‘include_dirs’: [
            ‘include/linux’,
          ],
        }],
        [‘OS=="win"’, {
          ‘defines’: [
            ‘WINDOWS_SPECIFIC_DEFINE’,
          ],
        }, { # OS != "win",
          ‘defines’: [
            ‘NON_WINDOWS_DEFINE’,
          ],
        }]
      ],
  ],
}

其中:

‘target_name’: 目标的名称,在所有.gyp文件中应该唯一。该名称将作为所产生的Visual Studio solution的名称。

‘type’: 设置为executable

‘msvs_guid’: 硬编码的GUID值,用在产生的Visual Studio solution文件上。

‘dependencies’: 本目标的依赖目标清单。gyp将保证所依赖的目标在本目标之前生成,还将链接dependencies清单中的库目标。

‘defines’: C预处理定义,等价于-D或者/D

‘include_dirs’: 头文件包含路径,等价于-I或/I

‘sources’: 本目标所包含的源文件

‘conditions’: 条件定义,可根据条件修改设置

一个典型的库目标(library target)的gyp文件

{
  ‘targets’: [
    {
      ‘target_name’: ‘foo’,
      ‘type’: ‘<(library)’
      ‘msvs_guid’: ’5ECEC9E5-8F23-47B6-93E0-C3B328B3BE65′,
      ‘dependencies’: [
        ‘xyzzy’,
        ‘../bar/bar.gyp:bar’,
      ],
      ‘defines’: [
        ‘DEFINE_FOO’,
        ‘DEFINE_A_VALUE=value’,
      ],
      ‘include_dirs’: [
        ‘..’,
      ],
      ‘direct_dependent_settings’: {
        ‘defines’: [
          ‘DEFINE_FOO’,
          ‘DEFINE_ADDITIONAL’,
        ],
        ‘linkflags’: [
        ],
      },
      ‘export_dependent_settings’: [
        ‘../bar/bar.gyp:bar’,
      ],
      ‘sources’: [
        ‘file1.cc’,
        ‘file2.cc’,
      ],
      ‘conditions’: [
        [‘OS=="linux"’, {
          ‘defines’: [
            ‘LINUX_DEFINE’,
          ],
          ‘include_dirs’: [
            ‘include/linux’,
          ],
        ],
        [‘OS=="win"’, {
          ‘defines’: [
            ‘WINDOWS_SPECIFIC_DEFINE’,
          ],
        }, { # OS != "win",
          ‘defines’: [
            ‘NON_WINDOWS_DEFINE’,
          ],
        }]
      ],
  ],
}

库目标的gyp文件大体上和可执行目标的gyp文件相同,除了以下几处不同:

‘type’: 通常设为’<(library)’,允许执行gyp时指定库是编译成静态库还是动态库。也可以直接给static_library或shared_library值直接指定库类型

‘direct_dependent_settings’: 定义设置,将应用到直接依赖该目标的其它目标上,也就是将本目标列在’dependencies’的那些目标上。

‘export_dependent_settings’: 定义设置,除了应用于直接依赖目标外,还可以应用在间接依赖目标上。

内置变量

gyp中变量是以<(variable_name)的形式使用的,除了可以自定变量外,gyp中还定义有一些系统变量。

变量名

变量值

DEPTH chromium顶层src目录的路径
PRODUCT_DIR 如果build release版本,值为<(DEPTH)/out/Release
如果build debug版本,值为<(DEPTH)/out/Debug
SHARED_INTERMEDIATE_DIR 动态库中间路径,值为<(PRODUCT_DIR)/obj/gen
   

chromium build脚本中开始出现android了

chromium的更新真是够疯狂的,一不小心开发版本就到16了。今天在看chromium源码时,无意中看到有关android的代码了,看来chromium android版本离我们不远了。

首先是build下多了install-build-deps-android.sh脚本,这个脚本是用来安装android SDK r13和android ndk r6b。难道不考虑android3.2以下版本?

build下还有一个all_android.gyp文件,里面只有一个target: android_builder_tests.

build下android文件夹只有一个文件envsetup.sh,在编译android版本前必须执行该脚本设置环境变量。

看了看源码树,只有base和net目录下有少量的android平台代码。

从现存的代码来看,似乎android移植才开了个头,不过最近有媒体爆料说即将发布的android 4.0中即将包含chromium浏览器。莫非chromium也会学习android,只进行有限的开放。

做了一个SVN的备份脚本

不知道公司租了个什么破写字楼,隔三差五的跳闸、停电。想想内部PC服务器也没有个UPS扛一下,真是捏把汗,万一硬盘坏了,平时又没有做备份,项目代码可就找不到了。因此就想到写一个脚本,定期的备份SVN库。我的备份策略是:

  • 每天进行一次增量备份。
  • 每周进行一次全备份。
  • 备份的文件复制到另一台服务器上。

因为服务器采用的是ubuntu系统,所以实现起来也比较顺利。linux中的cron搞定定时功能,而svn支持增量备份和全备份,使用scp复制文件到另外一个ubuntu服务器。由于scp复制过程中需要输入密码,就使用了expect脚本。

weekly-full-backup.sh脚本如下:

#!/bin/sh
#
# Perform a weekly backup of a subversion repository,
# logging the most-recently-backed-up revision so an
# incremental script can be run other days.

svn_repos="your svn repos";
backups_dir="your backup dir";
next_backup_file="xxx-weekly-full-backup.$(date +%Y%m%d)"
last_backed_rev="last_backed_rev"

youngest=$(svnlook youngest $svn_repos);

echo "Backing up to revision $youngest"
svnadmin dump $svn_repos > $backups_dir/$next_backup_file

echo "Compressing dump file …"
gzip -9 $backups_dir/$next_backup_file
echo $youngest > $backups_dir/$last_backed_rev

echo "$next_backup_file        rev 0  ~ $youngest" >> ${backups_dir}/readme

$backups_dir/cp-another-server.sh $backups_dir ${next_backup_file}.gz

daily-backup.sh脚本如下:

#!/bin/bash
#
# Perform a daily backup of a subversion repository,
# using the previous most-recently-backed-up revision
# to create just an incremental dump.

svn_repos="your svn repos"
backups_dir="your backup dir"
next_backup_file="xxx-daily-incremental-backup.$(date +%Y%m%d)"
last_backed_rev="last_backed_rev"

read previous_youngest < $backups_dir/$last_backed_rev

youngest=$(svnlook youngest $svn_repos)
if [ $youngest -eq $previous_youngest ]
then
    echo "No new revision to backup"
    exit
fi

# We need to backup from the last backed up revision
# to the lastest(youngest) revision in the repository
let first_rev=previous_youngest+1
last_rev=$youngest

echo "Backing up revision $first_rev to $last_rev …"

svnadmin dump -incremental -revision $first_rev:$last_rev $svn_repos > $backups_dir/$next_backup_file

echo "Compressing dump file …"
gzip -9 $backups_dir/$next_backup_file

echo $last_rev >$backups_dir/$last_backed_rev

echo "$next_backup_file        rev $first_rev ~ $last_rev" >> $backups_dir/readme

$backups_dir/cp-another-server.sh $backups_dir ${next_backup_file}.gz

cp-another-server.sh脚本如下:

#!/usr/bin/expect
if {$argc<2} {
    send_user "usage: $argv0 backup_dir filenamen"
    exit
}

set backup_dir [lindex $argv 0]
set backup_filename [lindex $argv 1]
send_user "execute:$argv0 $backup_dir $backup_filenamen"

spawn scp $backup_dir/$backup_filename alexz@10.3.48.8:/home/alexz/svn-backup/$backup_filename
expect {
    "*password*" {send "alexz123n"}
}

expect eof
exit

请注意,脚本中涉及到项目名、路径名的,都进行了替换。

chromium代码分析(1): test_shell

    chromium浏览器融合了众多开源软件,自身拥有一套复杂的framework。如果一头扎进chromium的代码,首先需要了解chrome的多进程模型、sandbox原理,这对理解webkit移植带来很大的难度。好在chromium中包含了一个测试程序test_shell,用来验证webkit chromium移植工作。test_shell代码位于:src/webkit/tools/test_shell下,test_shell具备浏览器的雏形,有前进/后退/刷新/停止/输入网址等功能,如果仅仅是做一个简单嵌入式的浏览器,使用这部分的代码就足够了。从test_shell入手,更有助于分析webkit chromium移植。

    test_shell运行界面如下:

linux版本的绘制过程

下面的图描述了linux下gtk/skia/cairo之间的关系

 

简单说,页面绘制过程就是先由Webkit渲染到skia的canvas上,然后在bitblt到Cairo surface上,最后到X server后端。

在webkit的chromium移植中,有一个重要的类:WebView(在Qt/android等移植中也有这样一个类),它运用了Facade设计模式,封装了webkit内部复杂的接口,对外提供一个一致/简单的接口,浏览器UI主要和这个WebView打交道。下面看看这个WebView是如何创建的?trace信息如下:

#0  WebKit::WebView::create (client=0x7ffff7f50000) at third_party/WebKit/Source/WebKit/chromium/src/WebViewImpl.cpp:235
#1  0x00000000004387c0 in WebViewHost::Create (parent_view=0x7fffed278830, delegate=0x7ffff7f50000, dev_tools_client=0×0, prefs=…)
    at webkit/tools/test_shell/webview_host_gtk.cc:30
#2  0x000000000042c576 in TestShell::Initialize (this=0x7ffff7e74f20, starting_url=…) at webkit/tools/test_shell/test_shell_gtk.cc:349
#3  0x00000000004245b1 in TestShell::CreateNewWindow (starting_url=…, result=0x7fffffffdc20) at webkit/tools/test_shell/test_shell.cc:203
#4  0x0000000000421e61 in main (argc=1, argv=0x7fffffffdfe8) at webkit/tools/test_shell/test_shell_main.cc:303

UML序列图如下:

Webkit chromium port中,图形后端是采用的skia,在SkCanvas上排版渲染,但最终呈现给用户的却是一个GtkFixed widget。skia canvas中的图像又是如何显示到GtkFixed widget上的呢?答案就在WebWidgetHost::Paint()方法中:

1)根据webkit排版的高度和宽度创建PlatformCanvas

2)webview将内容渲染到canvas

3)BitBlt到GtkFixed的gdk window上

这样webkit就将排版后的内容在gtk窗口中显示出来了。