從一個(gè) issue 出發(fā),帶你玩圖數(shù)據(jù)庫(kù) NebulaGraph 內(nèi)核開(kāi)發(fā)

如何 build NebulaGraph?如何為 NebulaGraph 內(nèi)核做貢獻(xiàn)?即便是新手也能快速上手,從本文作為切入點(diǎn)就夠了。

NebulaGraph 的架構(gòu)簡(jiǎn)介

為了方便對(duì) NebulaGraph 尚未了解的讀者也能快速直接從貢獻(xiàn)代碼為起點(diǎn)了解它,我把開(kāi)發(fā)、貢獻(xiàn)內(nèi)核代碼入手所需要的基本架構(gòu)知識(shí)在這里以最小信息量的形式總結(jié)一下。作為前導(dǎo)知識(shí),請(qǐng)資深的 NebulaGraph 玩家直接跳過(guò)這一章節(jié)。

服務(wù)、進(jìn)程

NebulaGraph 的架構(gòu)和 Google Spanner、TiDB 很相似,核心部分只有三種服務(wù)進(jìn)程:Graph 服務(wù)、Meta 服務(wù)和 Storage 服務(wù)。它們之間彼此通過(guò) TCP 之上的 Thrift RPC 協(xié)議進(jìn)行通信。

https://docs-cdn.nebula-graph.com.cn/docs-2.0/1.introduction/2.nebula-graph-architecture/nebula-graph-architecture-1.png

計(jì)算層與存儲(chǔ)層

NebulaGraph 是存儲(chǔ)與計(jì)算分離的架構(gòu),Meta 服務(wù)和 Storage 服務(wù)共同組成了存儲(chǔ)層,Graph 服務(wù)是內(nèi)核提供的計(jì)算層。

這樣的設(shè)計(jì)使得 NebulaGraph 的集群部署可以靈活按需分配計(jì)算、存儲(chǔ)的資源。比如,在同一個(gè)集群中創(chuàng)建不同配置的兩組 Graph 服務(wù)實(shí)例用來(lái)面向不同類(lèi)型的業(yè)務(wù)。

同時(shí),計(jì)算層解耦于存儲(chǔ)層使得在 NebulaGraph 之上的構(gòu)建不同的特定計(jì)算層成為可能。比如,NebulaGraph Algorithm、NebulaGraph Analytics 就是在 NebulaGraph 之上構(gòu)建了異構(gòu)的另一個(gè)計(jì)算層。任何人都可以按需定制專(zhuān)屬計(jì)算層,從而滿足統(tǒng)一圖基礎(chǔ)存儲(chǔ)之上的復(fù)合、多樣的計(jì)算需求。

Graph Service:nebula-graphd

Graph 服務(wù)是對(duì)外接收?qǐng)D庫(kù)登錄、圖查詢請(qǐng)求、集群管理操作、Schema 定義所直接連接的服務(wù),它的進(jìn)程名字叫 graphd,表示 nebula graph daemon。

Graph 服務(wù)的每一個(gè)進(jìn)程是無(wú)狀態(tài)的,這使得橫向擴(kuò)縮 Graph 服務(wù)的實(shí)例非常靈活、簡(jiǎn)單。

Graph 服務(wù)也叫 Query Engine,其內(nèi)部和傳統(tǒng)的數(shù)據(jù)庫(kù)系統(tǒng)的設(shè)計(jì)非常相似,分為:解析、校驗(yàn)、計(jì)劃、執(zhí)行幾部分。

image

Meta Service:nebula-metad

Meta 服務(wù)顧名思義負(fù)責(zé)元數(shù)據(jù)管理,進(jìn)程名字叫 metad。這些元數(shù)據(jù)包括:

  • 所有的圖空間、Schema 定義
  • 用戶鑒權(quán)、授權(quán)信息
  • 集群服務(wù)的發(fā)現(xiàn)與服務(wù)的分布
  • 圖空間中的數(shù)據(jù)分布

Meta 服務(wù)的進(jìn)程可以單實(shí)例部署。在非單機(jī)部署的場(chǎng)景下,為了數(shù)據(jù)、服務(wù)的高 SLA ,以奇數(shù)個(gè)實(shí)例進(jìn)行部署。通常來(lái)說(shuō) 3 個(gè) nebula-metad 就足夠了,3 個(gè) nebula-metad 通過(guò) Raft 共識(shí)協(xié)議構(gòu)成一個(gè)集群提供服務(wù)。

image

Storage Service:nebula-storaged

Storage 服務(wù)存儲(chǔ)所有的圖數(shù)據(jù),進(jìn)程名字叫 storaged。storaged 分布式地存儲(chǔ)圖數(shù)據(jù),為 Graph 內(nèi)部的圖查詢執(zhí)行期提供底層的圖語(yǔ)義存儲(chǔ)接口,方便 Storage 客戶端通過(guò) Thrift RPC 協(xié)議面向涉及的 storaged 示例進(jìn)行圖語(yǔ)義的讀寫(xiě)。

當(dāng) NebulaGraph 中圖空間的副本數(shù)大于 1 的時(shí)候,每一個(gè)分區(qū)都會(huì)在不同 storaged 示例上有副本,副本之間則通過(guò) Raft 協(xié)議協(xié)調(diào)同步與讀寫(xiě)。

image

進(jìn)程間通信、服務(wù)發(fā)現(xiàn)機(jī)制

在 NebulaGraph 中 graphd、metad、storaged 之間通過(guò) Thrift 協(xié)議進(jìn)行遠(yuǎn)程調(diào)用(RPC),下邊給一些例子:

  • graphd 會(huì)通過(guò) metaclient 調(diào)用 metad:將自己報(bào)告為一個(gè)正在運(yùn)行的服務(wù),以便被發(fā)現(xiàn);再為用戶(使用 graphclient )登錄進(jìn)行 RPC 調(diào)用;當(dāng)它處理 nGQL 查詢時(shí),獲取圖存儲(chǔ)分布情況;
  • graphd 會(huì)通過(guò) storageclient 調(diào)用 storaged:當(dāng) graphd 處理 nGQL 時(shí),先從 metad 獲得所需的元信息,再進(jìn)行圖數(shù)據(jù)的讀/寫(xiě);
  • storaged 會(huì)通過(guò) metaclient調(diào)用 metad:將 storaged 報(bào)告為一個(gè)正在運(yùn)行的服務(wù),以便被發(fā)現(xiàn)。

當(dāng)然,有狀態(tài)的存儲(chǔ)引擎內(nèi)部也有集群同步的流量與通信。比如,storaged 與其他 storaged 有 Raft 連接;metad 與其他 metad 實(shí)例有 Raft 連接。

開(kāi)發(fā)環(huán)境搭建

接下來(lái),我們開(kāi)始 NebulaGraph 的構(gòu)建、開(kāi)發(fā)環(huán)境的部分。

NebulaGraph 只支持在 GNU/Linux 分支中構(gòu)建。目前來(lái)說(shuō),最方便的方式是在社區(qū)預(yù)先提供好了依賴(lài)的容器鏡像的基礎(chǔ)上在容器內(nèi)部構(gòu)建、調(diào)試 NebulaGraph 代碼的更改和 Debug。

創(chuàng)建一個(gè)容器化的 NebulaGraph 集群

為了更方便地調(diào)試代碼,我習(xí)慣提前創(chuàng)建一個(gè) NebulaGraph Docker 環(huán)境。推薦使用官方的 Docker-Compose 方式部署,也可以使用我在官方 Docker-Compose 基礎(chǔ)之上弄的一鍵部署工具:nebula-up。

下面以 nebula-up 為例:

在 Linux 開(kāi)發(fā)服務(wù)器中執(zhí)行 curl -fsSL nebula-up.siwei.io/install.sh | bash 就可以了。

代碼獲取

NebulaGraph 的代碼倉(cāng)庫(kù)托管在 GitHub 之上,在聯(lián)網(wǎng)的情況下直接克?。?/p>

git clone git@github.com:vesoft-inc/nebula.git
cd nebula

創(chuàng)建開(kāi)發(fā)容器

有了 NebulaGraph 集群,我們可以借助 nebula-dev-docker 提供的開(kāi)箱即用開(kāi)發(fā)容器鏡像,搭建開(kāi)發(fā)環(huán)境:

export TAG=ubuntu2004
docker run -ti \
  --network nebula-net \
  --security-opt seccomp=unconfined \
  -v "$PWD":/home/nebula \
  -w /home/nebula \
  --name nebula_dev \
  vesoft/nebula-dev:$TAG \
  bash

其中,-v "$PWD" 表示當(dāng)前的 NebulaGraph 代碼本地的路徑會(huì)被映射到開(kāi)發(fā)容器內(nèi)部的 /home/nebula,而啟動(dòng)的容器名字是 nebula_dev。

待這個(gè)容器啟動(dòng)后,會(huì)自動(dòng)進(jìn)入到這個(gè)容器的 bash shell 之中。如果我們輸入 exit 退出容器,它會(huì)被關(guān)閉。如果我們想再次啟動(dòng)容器,只需要執(zhí)行:

docker start nebula_dev

之后的編譯、Debug、測(cè)試工作都在 nebula_dev 容器內(nèi)部進(jìn)行。在容器是運(yùn)行狀態(tài)的情況下,可以隨時(shí)新建一個(gè)容器內(nèi)部的 bash shell 進(jìn)程:

docker exec -ti nebula_dev bash

為了保持編譯環(huán)境是最新版,可以定期刪除、拉取、重建這個(gè)開(kāi)發(fā)容器,以保持環(huán)境與代碼相匹配。

編譯環(huán)境

nebula_dev 這個(gè)容器內(nèi)部,我們可以進(jìn)行代碼編譯。進(jìn)入編譯容器:

docker exec -ti nebula_dev bash

用 CMake 準(zhǔn)備 makefile。第一次構(gòu)建時(shí),為了節(jié)省時(shí)間、內(nèi)存,我關(guān)閉了測(cè)試 -DENABLE_TESTING=OFF

mkdir build && cd build
cmake -DCMAKE_CXX_COMPILER=$TOOLSET_CLANG_DIR/bin/g++ -DCMAKE_C_COMPILER=$TOOLSET_CLANG_DIR/bin/gcc -DENABLE_WERROR=OFF -DCMAKE_BUILD_TYPE=Debug -DENABLE_TESTING=OFF ..

開(kāi)始編譯,根據(jù)服務(wù)器的空閑 CPU 個(gè)數(shù)和內(nèi)存量力而行。比如,我在 72 核心的服務(wù)器上準(zhǔn)備允許同時(shí)運(yùn)行 64 個(gè) job,則運(yùn)行:

make -j64

第一次構(gòu)建的時(shí)間會(huì)慢一些,在 make 成功之后,我們也可以執(zhí)行 make install 把二進(jìn)制安裝到像生產(chǎn)安裝時(shí)候一樣的路徑:

root@1827b82e88bf:/home/nebula/build# make install

root@1827b82e88bf:/home/nebula/build# ls /usr/local/nebula/bin
db_dump  db_upgrader  meta_dump  nebula-graphd  nebula-metad  nebula-storaged

root@1827b82e88bf:/home/nebula/build# ls /usr/local/nebula/
bin  etc  pids  scripts  share

調(diào)試 NebulaGraph

以 graphd 調(diào)試為例。

安裝依賴(lài)

安裝一些后邊會(huì)方便 Debug 額外用到的依賴(lài):

# 裝一個(gè) ping,測(cè)試一下 nebula-up 安裝的集群可以訪問(wèn)
apt update && apt install iputils-ping -y
# ping graphd 試試看
ping graphd -c 4

# 安裝 gdb gdb-dashboard
apt install gdb -y
wget -P ~ https://git.io/.gdbinit
pip install pygments

準(zhǔn)備客戶端

準(zhǔn)備一個(gè) NebulaGraph 的命令行客戶端:

# 新開(kāi)一個(gè) nebula_dev 的 shell
docker exec -ti nebula_dev bash

# 下載 nebula-console 二進(jìn)制文件,并賦予可執(zhí)行權(quán)限,命名為 nebula-console 并安裝到 /usr/bin/ 下
wget https://github.com/vesoft-inc/nebula-console/releases/download/v3.2.0/nebula-console-linux-amd64-v3.2.0
chmod +x nebula-console*
mv nebula-console* /usr/bin/nebula-console

連接到前邊我們 nebula-up 準(zhǔn)備的集群之上,加載 basketballplayer 這個(gè)測(cè)試數(shù)據(jù):

nebula-console -u root -p nebula --address=graphd --port=9669
:play basketballplayer;
exit

gdb 運(yùn)行 graphd

用 gdb 執(zhí)行剛剛編譯的 nebula-graphd 二進(jìn)制,讓它成為一個(gè)新的 graphd 服務(wù),名字就叫 nebula_dev。

首先啟動(dòng) gdb:

# 新開(kāi)一個(gè) nebula_dev 的 shell
docker exec -ti nebula_dev bash

cd /usr/local/nebula/
mkdir -p /home/nebula/build/log
gdb bin/nebula-graphd

在 gdb 內(nèi)部執(zhí)行設(shè)置必要的參數(shù),跟隨 fork 的子進(jìn)程:

set follow-fork-mode child

設(shè)置待調(diào)試 graphd 的啟動(dòng)參數(shù)(配置):

  • meta_server_addrs 填已經(jīng)啟動(dòng)的集群的所有 metad 的地址;
  • local_ipws_ip 填本容器的域名,port 是 graphd 監(jiān)聽(tīng)端口;
  • log_dir 是輸出日志的目錄,vminloglevel 是日志的輸出等級(jí);
set args --flagfile=/usr/local/nebula/etc/nebula-graphd.conf.default \
    --meta_server_addrs=metad0:9559,metad1:9559,metad2:9559 \
    --port=9669 \
    --local_ip=nebula_dev \
    --ws_ip=nebula_dev \
    --ws_http_port=19669 \
    --log_dir=/home/nebula/build/log \
    --v=4 \
    --minloglevel=0

如果我們想加斷點(diǎn)在 src/common/function/FunctionManager.cpp 2783 行,可以再執(zhí)行:

b /home/nebula/src/common/function/FunctionManager.cpp:2783

配置前邊安裝的 gdb-dashboard,一個(gè)開(kāi)源的 gdb 界面插件。

# 設(shè)定在 gdb 界面上展示 代碼、歷史、回調(diào)棧、變量、表達(dá)幾個(gè)部分,詳細(xì)參考 https://github.com/cyrus-and/gdb-dashboard
dashboard -layout source history stack variables expressions

最后我們讓進(jìn)程通過(guò) gdb 跑起來(lái)吧:

run

之后,我們就可以在這個(gè)窗口/shell 會(huì)話下調(diào)試 graphd 程序了。

修改 NebulaGraph 代碼

這里,我以 issue#3513 為例子,快速介紹一下代碼修改的過(guò)程。

讀代碼

這個(gè) issue 表達(dá)的內(nèi)容是在有一小部分用戶決定把 JSON 以 String 的形式存儲(chǔ)在 NebulaGraph 中的屬性里。因?yàn)檫@種方式比較罕見(jiàn)且不被推崇,NebulaGraph 沒(méi)有直接支持對(duì) JSON String 解析。

由于不是一個(gè)通用型需求,這個(gè)功能是希望熱心的社區(qū)用戶自己來(lái)實(shí)現(xiàn)并應(yīng)用在他的業(yè)務(wù)場(chǎng)景中。但在該 issue 中,剛好有位新手貢獻(xiàn)者在里邊回復(fù)、求助如何開(kāi)始參與這塊的功能實(shí)現(xiàn)。借著這個(gè)契機(jī),我去參與討論看了一下這個(gè)功能可以實(shí)現(xiàn)成什么樣子。最終討論的結(jié)果是可以做成和 MySQL 中的 JSON_EXTRACT 函數(shù)那樣,改為只接受 JSON String、無(wú)需處理輸出路徑參數(shù)。

一句話來(lái)說(shuō)就是,為 NebulaGraph 引入一個(gè)解析 JSON String 為 Map 的函數(shù)。那么,如何實(shí)現(xiàn)這個(gè)功能呢?

在哪里修改

顯然,引入新的函數(shù),項(xiàng)目變更肯定有很多。所以,我們只需要找到之前增加新函數(shù)的 PR 就可以快速知道在哪些地方修改了。

一般情況下,可以自底向上地了解 NebulaGraph 整體的代碼結(jié)構(gòu),再一點(diǎn)點(diǎn)找到函數(shù)處理的位置。這時(shí)候,除了代碼本身,一些面向貢獻(xiàn)者的文章可能會(huì)幫助大家事半功倍對(duì)整體有一個(gè)了解。NebulaGraph 官方也除了一個(gè)系列文章,大家做項(xiàng)目貢獻(xiàn)前不妨閱讀了解下,參見(jiàn):延伸閱讀 5。

具體的實(shí)操起來(lái)呢?我從 pr#4526 了解到所有函數(shù)入口都被統(tǒng)一管理在 src/common/function/FunctionManager.cpp 之中。通過(guò)搜索、理解當(dāng)中某個(gè)函數(shù)的關(guān)鍵詞之后,可以很容易理解一個(gè)函數(shù)實(shí)體的關(guān)鍵詞、輸入/輸出數(shù)據(jù)類(lèi)型、函數(shù)體處理邏輯的代碼在哪里實(shí)現(xiàn)。

此外,在同一個(gè)根目錄下,src/common/function/test/FunctionManagerTest.cpp 之中則是所有這些函數(shù)的單元測(cè)試代碼。用同樣的方式也可以知道新加的一個(gè)函數(shù)需要如何在里邊實(shí)現(xiàn)基于 gtest 的單元測(cè)試。

開(kāi)始改代碼

在修改代碼之前,確保在最新的 master 分支之上創(chuàng)建一個(gè)單獨(dú)的分支。在這里的例子中,我把分支名字叫 fn_JSON_EXTRACT

git checkout master
git pull
git checkout -b fn_JSON_EXTRACT

通過(guò) Google 了解與交叉驗(yàn)證 NebulaGraph 內(nèi)部使用的 utils 庫(kù),知道應(yīng)該用 folly::parseJson 把字符串讀成 folly::dynamic。再 cast 成 NebulaGraph 內(nèi)置的 Map() 類(lèi)型。最后,借助于 Stack Overflow/GitHub Copilot,我終于完成了第一個(gè)版本的代碼修改。

調(diào)試代碼

我興沖沖地改好了第一版的代碼,信心滿滿地開(kāi)始編譯!實(shí)際上,因?yàn)槲沂?CPP 新手,即使在 Copilot 加持下,我的代碼還是花了好幾次修改才通過(guò)編譯。

編譯之后,我用 gdb 把修改了的 graphd 啟動(dòng)起來(lái)。用 console 發(fā)起 JSON_EXTRACT 的函數(shù)調(diào)用。先調(diào)通了期待中的效果,并試著跑幾種異常的輸入。在發(fā)現(xiàn)新問(wèn)題、修改、編譯、調(diào)試的幾輪循環(huán)下讓代碼達(dá)到了期望的狀態(tài)。

這時(shí)候,就該把代碼提交到遠(yuǎn)端 GitHub 請(qǐng)項(xiàng)目的資深貢獻(xiàn)者幫忙 review 啦!

提交 PR

PR(Pull Request)是 GitHub 中方便多人代碼協(xié)作、代碼審查中的一種方式。它通過(guò)把一個(gè) repo 下的分支與這個(gè)審查協(xié)作的實(shí)例(PR)做映射,得到一個(gè)項(xiàng)目下唯一的 PR 號(hào)碼之后,生成單獨(dú)的網(wǎng)頁(yè)。在這個(gè)網(wǎng)頁(yè)下,我們可以做不同貢獻(xiàn)者之間的交流和后續(xù)的代碼更新。這個(gè)過(guò)程中,代碼提交者們可以一直在這個(gè)分支上不斷提交代碼直到代碼的狀態(tài)被各方同意 approve,再合并 merge 到目的分支中。

這個(gè)過(guò)程可以分為:

  • 創(chuàng)建 GitHub 上遠(yuǎn)程的個(gè)人開(kāi)發(fā)分支;
  • 基于分支創(chuàng)建目標(biāo)項(xiàng)目倉(cāng)庫(kù)中的 PR;
  • 在 PR 中協(xié)作、討論、不斷再次提交到開(kāi)發(fā)分支直到多方達(dá)到合并、或者關(guān)閉的共識(shí);

提交到個(gè)人遠(yuǎn)程分支

在這一步驟里,我們要把當(dāng)前的本地提交的 commit 提交到自己的 GitHub 分叉之中。

commit 本地修改

首先,確認(rèn)本地的修改是否都是期待中的:

# 先確定修改的文件
$ git status
# 再看看修改的內(nèi)容
$ git diff

再 commit,這時(shí)候是在本地倉(cāng)庫(kù)提交 commit:

# 添加所有當(dāng)前目錄(. 這個(gè)點(diǎn)表示當(dāng)前目錄)修改過(guò)的文件為待 commit
$ git add .
# 然后我們可以看一下?tīng)顟B(tài),這些修改的文件狀態(tài)已經(jīng)不同了
$ git status
# 最后,提交在本地倉(cāng)庫(kù),并用 -m 參數(shù)指定單行的 commit message
$ git commit -m "feat: introduce function JSON_EXTRACT"

提交到自己遠(yuǎn)程的分支

在提交之前,要確保自己的 GitHub 賬號(hào)之下確實(shí)存在 NebulaGraph 代碼倉(cāng)庫(kù)的分叉 fork。比如,我的 GitHub 賬號(hào)是 wey-gu,那么我對(duì) https://github.com/vesoft-inc/nebula 的分叉應(yīng)該就是 https://github.com/wey-gu/nebula

如果還沒(méi)有自己的分叉,可以直接在 https://github.com/vesoft-inc/nebula 上點(diǎn)擊右上角的 Fork,創(chuàng)建自己的分叉?zhèn)}庫(kù)。

當(dāng)遠(yuǎn)程的個(gè)人分叉存在之后,我們可以把代碼提交上去:

# 添加一個(gè)新的遠(yuǎn)程倉(cāng)庫(kù)叫 wey
git remote add wey git@github.com:wey-gu/nebula.git
# 提交 JSON_EXTRACT 分支到 wey 這個(gè) remote 倉(cāng)庫(kù)
git push wey JSON_EXTRACT

在個(gè)人遠(yuǎn)程分叉分支上創(chuàng)建 PR

這時(shí)候,我們?cè)L問(wèn)這個(gè)遠(yuǎn)程分支:https://github.com/wey-gu/nebula/tree/fn_JSON_EXTRACT,就能找到 Open PR 的入口:

image

點(diǎn)擊 Open pull request 按鈕,進(jìn)入到創(chuàng)建 PR 的界面了,這和在一般的論壇里提交一個(gè)帖子是很類(lèi)似的:

image

提交之后,我們可以等待、或者邀請(qǐng)其他人來(lái)做代碼的審查 review。往往,開(kāi)源項(xiàng)目的貢獻(xiàn)者們會(huì)從他們的各自角度給出代碼修改、優(yōu)化的建議。經(jīng)過(guò)幾輪的代碼修改、討論后,這時(shí)候代碼會(huì)達(dá)到最佳的狀態(tài)。

在這些審查者中,除了社區(qū)的貢獻(xiàn)者(人類(lèi))之外,還有自動(dòng)化的機(jī)器人。它們會(huì)在代碼庫(kù)中自動(dòng)化地通過(guò)持續(xù)集成 CI 的方式運(yùn)行自動(dòng)化的審查工作,可能包括以下幾種:

  • CLA:Contributor License Agreement,貢獻(xiàn)者許可協(xié)議。PR 作者在首次提交代碼到項(xiàng)目時(shí),所需簽署的協(xié)議。因?yàn)榇a將被提交到公共空間,這份協(xié)議的簽署意味著作者同意代碼被分享、復(fù)用、修改;
  • lint:代碼風(fēng)格檢查,這也是最常見(jiàn)的 CI 任務(wù);
  • test:各種層面的測(cè)試檢查任務(wù)。

通常來(lái)說(shuō),所有自動(dòng)化審查機(jī)器人執(zhí)行的任務(wù)全都通過(guò)后,貢獻(xiàn)的代碼狀態(tài)才能被認(rèn)為是可合并的。不出意外,我首次提交的代碼果然有測(cè)試的失敗提示。

image

調(diào)試 CI 測(cè)試代碼

NebulaGraph 里所有的 CI 測(cè)試代碼都能在本地被觸發(fā)。當(dāng)然,它們都有被單獨(dú)觸發(fā)的方式。我們需要掌握如何單獨(dú)觸發(fā)某個(gè)測(cè)試,而不是在每次修改一個(gè)小的測(cè)試修復(fù)、提交到服務(wù)器,就等著 CI 做全量的運(yùn)行,這樣會(huì)浪費(fèi)掉幾十分鐘。

CTest

本次 PR 提交中,我修改的函數(shù)代碼同一層級(jí)下的單元測(cè)試 CTest 就有問(wèn)題。問(wèn)題發(fā)生的原因有多種,可能是測(cè)試代碼本身、代碼變更破壞了原來(lái)的測(cè)試用例、測(cè)試用例發(fā)現(xiàn)代碼修改本身的問(wèn)題。

我們要根據(jù) CTest 失敗的報(bào)錯(cuò)進(jìn)行排查和代碼修改。再編譯代碼,在本地運(yùn)行一下這個(gè)失敗的用例:

# 我們需要進(jìn)入到我們的編譯容器內(nèi)部的 build 目錄下
$ docker exec -ti nebula_dev bash
$ cd build
# 在 -DENABLE_TESTING=ON 之中編譯,如果之前的編譯 job 數(shù)下內(nèi)存已經(jīng)跑滿了的話,這次可以把 job 數(shù)調(diào)小一點(diǎn),因?yàn)殚_(kāi)啟測(cè)試會(huì)占用更多內(nèi)存
$ cmake -DCMAKE_CXX_COMPILER=$TOOLSET_CLANG_DIR/bin/g++ -DCMAKE_C_COMPILER=$TOOLSET_CLANG_DIR/bin/gcc -DENABLE_WERROR=OFF -DCMAKE_BUILD_TYPE=Debug -DENABLE_TESTING=ON ..
$ make -j 48

# 可以看到編譯成功了 CTest 的單元測(cè)試二進(jìn)制可執(zhí)行文件
# [100%] Linking CXX executable ../../../../bin/test/function_manager_test
# [100%] Built target function_manager_test

# 執(zhí)行重新修改過(guò)的單元測(cè)試!
$ bin/test/function_manager_test

[==========] Running 11 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 11 tests from FunctionManagerTest
[ RUN      ] FunctionManagerTest.testNull
[       OK ] FunctionManagerTest.testNull (0 ms)
[ RUN      ] FunctionManagerTest.functionCall
W20221020 23:35:18.579897 28679 Map.cpp:77] JSON_EXTRACT nested layer 1: Map can be populated only by Bool, Double, Int, String value and null, now trying to parse from: object
[       OK ] FunctionManagerTest.functionCall (2 ms)
[ RUN      ] FunctionManagerTest.time
[       OK ] FunctionManagerTest.time (0 ms)
[ RUN      ] FunctionManagerTest.returnType
[       OK ] FunctionManagerTest.returnType (0 ms)
[ RUN      ] FunctionManagerTest.SchemaRelated
[       OK ] FunctionManagerTest.SchemaRelated (0 ms)
[ RUN      ] FunctionManagerTest.ScalarFunctionTest
[       OK ] FunctionManagerTest.ScalarFunctionTest (0 ms)
[ RUN      ] FunctionManagerTest.ListFunctionTest
[       OK ] FunctionManagerTest.ListFunctionTest (0 ms)
[ RUN      ] FunctionManagerTest.duplicateEdgesORVerticesInPath
[       OK ] FunctionManagerTest.duplicateEdgesORVerticesInPath (0 ms)
[ RUN      ] FunctionManagerTest.ReversePath
[       OK ] FunctionManagerTest.ReversePath (0 ms)
[ RUN      ] FunctionManagerTest.DataSetRowCol
[       OK ] FunctionManagerTest.DataSetRowCol (0 ms)
[ RUN      ] FunctionManagerTest.PurityTest
[       OK ] FunctionManagerTest.PurityTest (0 ms)
[----------] 11 tests from FunctionManagerTest (5 ms total)

[----------] Global test environment tear-down
[==========] 11 tests from 1 test suite ran. (5 ms total)
[  PASSED  ] 11 tests.

成功!

將新的更改提交到遠(yuǎn)程分支上,在 PR 的網(wǎng)頁(yè)中,我們可以看到 CI 已經(jīng)在新的提交的觸發(fā)下重新編譯、執(zhí)行了。過(guò)一會(huì)兒全部 pass,我開(kāi)始興高采烈地等待著 2 位以上的審查者幫忙批準(zhǔn)代碼,最后合并它!

但是,我收到了新的建議:

image

另一位貢獻(xiàn)者請(qǐng)我添加 TCK 的測(cè)試用例。

TCK

TCK 的全稱(chēng)是 The Cypher Technology Compatibility Kit,它是 NebulaGraph 從 openCypher 社區(qū)繼承演進(jìn)而來(lái)的一套測(cè)試框架,并用 Python 做測(cè)試用例格式兼容的實(shí)現(xiàn)。

它的優(yōu)雅在于,我們可以像寫(xiě)英語(yǔ)一樣去描述我們想實(shí)現(xiàn)的端到端功能測(cè)試用例,像這樣!

# tests/tck/features/function/json_extract.feature
Feature: json_extract Function

  Background:
    Test json_extract function

  Scenario: Test Positive Cases
    When executing query:
      """
      YIELD JSON_EXTRACT('{"a": "foo", "b": 0.2, "c": true}') AS result;
      """
    Then the result should be, in any order:
      | result                      |
      | {a: "foo", b: 0.2, c: true} |
    When executing query:
      """
      YIELD JSON_EXTRACT('{"a": 1, "b": {}, "c": {"d": true}}') AS result;
      """
    Then the result should be, in any order:
      | result                      |
      | {a: 1, b: {}, c: {d: true}} |
    When executing query:
      """
      YIELD JSON_EXTRACT('{}') AS result;
      """
    Then the result should be, in any order:
      | result |
      | {}     |

在添加了自己的一個(gè)新的 tck 測(cè)試用例文本文件之后,我們只需要在測(cè)試文件中臨時(shí)增加標(biāo)簽,并在執(zhí)行的時(shí)候指定標(biāo)簽,就可以單獨(dú)執(zhí)行新增的 tck 測(cè)試用例了:

# 還是在編譯容器內(nèi)部,進(jìn)入到 tests 目錄下
cd ../tests
# 安裝 tck 測(cè)試所需依賴(lài)
python3 -m pip install -r requirements.txt
python3 -m pip install nebula3-python==3.1.0
# 運(yùn)行一個(gè)單獨(dú)為 tck 測(cè)試準(zhǔn)備的集群
make CONTAINERIZED=true ENABLE_SSL=true CA_SIGNED=true up
# 給 tests/tck/features/function/json_extract.feature 以@開(kāi)頭第一行加上標(biāo)簽,比如 @wey
vi tests/tck/features/function/json_extract.feature
# 執(zhí)行 pytest (包含 tck 用例),因?yàn)橹贫?-m "wey",只有 tests/tck/features/function/json_extract.feature 會(huì)被執(zhí)行
python3 -m pytest -m "wey"
# 關(guān)閉 pytest 所依賴(lài)的集群
make CONTAINERIZED=true ENABLE_SSL=true CA_SIGNED=true down

再次邀請(qǐng) review

待我們把需要的測(cè)試調(diào)通、再次提交 PR 并且 CI 用例全都通過(guò)之后,我們可以再次邀請(qǐng)之前幫助審查代碼的同學(xué)做做最后的查看,如果一切都順利,代碼就會(huì)被合并了!

image

就這樣,我的第一個(gè) CPP PR 終于被合并成功,大家能看到我留在 NebulaGraph 中的代碼了。

延伸閱讀:

  1. 基于 BDD 理論的 NebulaGraph 集成測(cè)試框架重構(gòu),https://nebula-graph.com.cn/posts/bdd-testing-practice
  2. 如何向 NebulaGraph 增加一個(gè)測(cè)試用例,https://nebula-graph.com.cn/posts/bdd-testing-practice-add-test-case
  3. NebulaGraph 文檔之架構(gòu)介紹,https://docs.nebula-graph.com.cn/master/1.introduction/3.nebula-graph-architecture/1.architecture-overview/
  4. NebulaGraph 源碼解讀系列,https://www.nebula-graph.com.cn/posts/nebula-graph-source-code-reading-00

謝謝你讀完本文 (///▽///)

如果你想嘗鮮圖數(shù)據(jù)庫(kù) NebulaGraph,記得去 GitHub 下載、使用、(з)-☆ star 它 -> GitHub;和其他的 NebulaGraph 用戶一起交流圖數(shù)據(jù)庫(kù)技術(shù)和應(yīng)用技能,留下「你的名片」一起玩耍呀~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容