利用开源和免费资源搭建CI环境
Tag CI, 测试, on by view 7586

前段时间偶然接触到了持续集成(Continuous Integration)这一概念,并对它做了进一步的了解;说是偶然接触到是有原因的,起初我在github上的某些项目里面发现了这些东西

image.png

吸引我的注意力的正是这些绿色的徽章,点入build那个徽章一看,连接到了一个叫做Travis CI的网站,里面有类似控制台输出的信息

image1.png

这个的确是控制台输出,看里面的内容知道是通过git clone将这个项目下载到某台linux服务器,然后sudo权限执行了一些shell脚本,再后来就是./configure、make、make install之类的,这整个流程就是linux用户通过代码安装某个软件的过程。当然,部分项目里面并没有make install,而是make test,这是执行单元测试啊。

经过进一步了解后,发现Travis CI就是一个专门提供在线编译以及运行单元测试等工作的一个云平台(或者直白点说就是服务器)。阅读了一下午的Travis CI英文文档后,自己尝试着利用Travis CI为自己的项目也创建一个这样的编译测试环境。

首先,Travis  CI是可以直接与Github连接,读取项目信息的。然后只要你的项目的根目录下面有一个名为.travis.yml的文件Travis CI即可识别你的项目,并且从这个文件里面读取它所要执行的步骤。当然.travis.yml这个配置文件必须遵守文档中所定义的各种约定,在这之前.travis.yml文件的内容也必须遵守.yml文件的语法格式约定(我有一次就是因为没注意到其中的语法错误导致排错很久)。

  • yml文件语法应当注意的内容

    例如:

    env:
    global:
       # The next declration is the encrypted COVERITY_SCAN_TOKEN, created
       #   via the "travis encrypt" command using the project repo's public key
       - secure: "q5Wb1ChR5r/52zmkssky4IIM2C3b/ItLjfGRuqPgAsU6pgnrJ7GvHC44A/jvba4rTElxWKXhTyWvskdwSzgCvBs0GIRXHxSYjtcD9IzIMLX7zHfczP2ekH2xGONXhZR1AWN6/YHh1QZ2SwdrlbQv9jybZ9NxWvk9v2IPhdWr5MM="
    language: c
    compiler: 
        - "gcc"
        - "clang"
    script:
        - "chmod 777 ./coverity.sh"
        - "./coverity.sh"

           

    这其中有部分语法类似于key: value,但是要注意:后面必须有一个空格符,另外,语法还有与python类似的对齐规则,没对齐或者是伪对齐(空格与Tab符对齐)都会导致语法错误。最终的结果是Travis CI报错。

  • compiler项是设置系统编译器,上例中是使用gcc和clang编译器,因此会有两次编译,一次是gcc一次clang编译,当然多个值就会有多个编译,众多的编译构成编译矩阵,可以测试某个项目在不同的环境下面的编译结果。script项是命令Travis CI执行的具体命令,上例中我将命令全部写入到一个shell文件里面去了,script这一项的内容是执行这个shell脚本。详细的配置介绍可以自己查看Travis CI官网上的文档。

利用Travis CI你可以做些什么?首先,检测代码在语法上的正确性这是最基本的,简单来说就是检测你的项目是否能够编译通过,是否能够在不同个环境下编译通过。其次,你可以利用一些单元测试对项目进行更进一步的检测,这些根据你的测试代码可以检测出一些逻辑上的错误。

其实Travis CI是可以有其他的一些功能的,比如程序发布。例如,我现在需要将编译后的可执行文件发布到项目的官方网站上供别人下载;我可以使用curl命令将可执行文件通过我事先准备好的上传接口上传到我的网站,这样一来,你每次更新代码Travis CI会自动编译完成后直接发送到你指定的网站上,然后你的程序发布可以自动的保持最新版本。

事实上类似于Travis CI的网站还比较多,比如drone.io等,当然还有一些收费的CI服务网站,这儿就不提了。最后值得一提的是虽然大部分的免费CI是Linux环境的,但是被我发现了一个Windows环境的CI,而且它有几种环境可以选择,例如Windows Server 2008,最重要的是它也是免费的而且支持许多版本的Visual Studio,也就是说C#、.NET或者是包含Windows API的这些仅能在Windows环境下编译的代码也能够编译,https://ci.appveyor.com就是它,Windows应用程序开发者的福音。


fork()函数
Tag fork, 进程, on by view 5653

fork函数可以复制一个当前进程。看代码

#include <stdio.h>
int main(void){
    int pid;
    pid=fork();
    if(pid>0){//在父进程
        printf("Parents Process, The son pid: %d\n", pid);
    }else if(0==pid){//在子进程
        printf("Son Process, The pid return: %d\n", pid);
    }else{
        printf("Error!\n");
    };
    for(;1;){
        sleep(1);
    }
    return 0;
}

为了让程序不会运行结束自动退出我使用for死循环和sleep函数将程序阻塞,fork函数会创建一个进程,至於是否创建了进程我们看任务管理器的截图

t1.png

任务管理器中有两个相同名称的进程了,这说明进程创建成功。 看终端运行结果
 t2.png

结果表明程序成功的创建了一个新进程。 fork()函数可以创建一个新进程并且返回子进程的ID,子进程与父进程完全一样,即执行相同的代码可是当子进程执行到fork()函数处的时候返回的值为0,却没有再创建进程;而父进程执行到fork()处创建了这个子进程并且返回了子进程的ID,至於为什么子进程不继续创建子子进程,这个我暂时就不知道了。 后来我又到fork()函数之前添加了一个printf作为标识,代码如下

#include <stdio.h>
int main(void){
    int pid;
    printf("hello\n";);
    pid=fork();
    if(pid>0){//在父进程
        printf("Parents Process, The son pid: %d\n", pid);
    }else if(0==pid){//在子进程
        printf("Son Process, The pid return: %d\n", pid);
    }else{
        printf("Error!\n");
    };
    for(;1;){
        sleep(1);
    }
    return 0;
}

运行,终端截图

t3.png

图中并没有两行hello,这说明,如果子进程是完全复制父进程从代码的第一行开始执行的话子进程也会打印hello,很明显子进程不是这样干的。 再改代码,在fork函数之后添加printf,为了避开if分支就把printf函数添加到for阻塞代码的前一行[14],代码如下

#include <stdio.h>
int main(void){
    int pid;
    printf("hello\n");
    pid=fork();
    if(pid>0){//在父进程
        printf("Parents Process, The son pid: %d\n", pid);
    }else if(0==pid){//在子进程
        printf("Son Process, The pid return: %d\n", pid);
    }else{
        printf("Error!\n");
    };
    printf("world\n");
    for(;1;){
        sleep(1);
    }
    return 0;
}

运行截图

t4.png

是的,结果如我所料,有两个world,这说明fork后的进程并不是从头开始执行的,而是从fork语句处开始执行的。这不就是在fork函数那个点产生了一个分支吗。对于长期在github里面混的我,这让我想起了github项目管理里面的fork,看样子他们的表达是一样的,github的fork截图

t5.png

你理解了fork吗?反正我是懂了……


Java线程编程
Tag java, 线程, on by view 3913

最近学习了一下网络编程,因此就无可避免的学习了多线程编程,先后经过在C#、C以及Java环境下编程,有所斩获。以下是Java环境下线程编程的一些总结:

  1. 创建线程

  2. 实例化Thread类

    Receive rcv;
    Thread th1=new Thread(rcv);

    其中Receive rcv要通过override抽象类Runnable来实现

  3. 重载抽象类Runnable

  4. 主要是重载其中的run方法,因为线程启动的目标函数就是重载后的run()方法,示例如下:

     public class Receive implements Runnable{
      @Override
      public void run() {
        System.out.print("sdjfhsdjf");
      }
      public static Receive init(){
        return new Receive();
      }
  5. 启动线程

  6. th1.start();

    线程启动后将会执行rcv.run();