Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 免费精品久久,亚洲欧美日韩一级特黄在线,成人在线免费小视频

          整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          Golang-實(shí)現(xiàn)一個(gè)簡單的DSL解釋器

          Golang:實(shí)現(xiàn)一個(gè)簡單的DSL解釋器

          o-實(shí)現(xiàn)一個(gè)簡單的DSL

          什么是DSL

          DSL 是 Domain Specific Language 的縮寫,中文翻譯為領(lǐng)域特定語言(下簡稱 DSL);而與 DSL 相對的就是 GPL,這里的 GPL 并不是我們知道的開源許可證,而是 General Purpose Language 的簡稱,即通用編程語言,也就是我們非常熟悉的 Objective-C、Java、Python 以及 C 語言等等。

          簡單說,就是為了解決某一類任務(wù)而專門設(shè)計(jì)的計(jì)算機(jī)語言。

          • Regex
          • SQL
          • HTML&CSS

          共同特點(diǎn)

          沒有計(jì)算和執(zhí)行的概念;

          • 其本身并不需要直接表示計(jì)算;
          • 使用時(shí)只需要聲明規(guī)則、事實(shí)以及某些元素之間的層級和關(guān)系;
          • 總結(jié)起來一句話:表達(dá)能力有限,通過在表達(dá)能力上做的妥協(xié)換取在某一領(lǐng)域內(nèi)的高效 那么DSL解釋器的主要功能是解釋執(zhí)行DSL

          設(shè)計(jì)原則

          實(shí)現(xiàn)DSL總共需要完成兩部分工作:

          設(shè)計(jì)語法和語義,定義 DSL 中的元素是什么樣的,元素代表什么意思 實(shí)現(xiàn) parser,對 DSL 解析,最終通過解釋器來執(zhí)行 那么我們可以得到DSL的設(shè)計(jì)原則:

          簡單

          • 學(xué)習(xí)成本低,DSL語法最好和部門主要技術(shù)棧語言保持一致(go,php)
          • 語法簡單,刪減了golang大部分的語法,只支持最基本的數(shù)據(jù)格式,二元運(yùn)算符,控制語句少量的語法糖嵌入式DSL
          • DSL需要嵌入到現(xiàn)有的編程語言中,發(fā)揮其實(shí)時(shí)解釋執(zhí)行且部署靈活的特點(diǎn)
          • 使用json類型的context與外部系統(tǒng)進(jìn)行通信,且提供與context操作相關(guān)的語法糖

          解釋器工作流程

          大部分編譯器的工作可以被分解為三個(gè)主要階段:解析(Parsing),轉(zhuǎn)化(Transformation)以及 代碼生成(Code Generation)

          • 解析 將源代碼轉(zhuǎn)換為一個(gè)更抽象的形式。
          • 轉(zhuǎn)換 接受解析產(chǎn)生的抽象形式并且操縱這些抽象形式做任何編譯器想讓它們做的事。
          • 代碼生成 基于轉(zhuǎn)換后的代碼表現(xiàn)形式(code representation)生成目標(biāo)代碼。

          解析

          • 詞法分析 —— tokenizer 通過一個(gè)叫做tokenizer(詞素生成器,也叫l(wèi)exer)的工具將源代碼分解成一個(gè)個(gè)詞素。(詞素是描述編程語言語法的對象。它可以描述數(shù)字,標(biāo)識(shí)符,標(biāo)點(diǎn)符號,運(yùn)算符等等。)
          • 語法分析 —— parser 接收詞素并將它們組合成一個(gè)描述了源代碼各部分之間關(guān)系的中間表達(dá)形式:抽象語法樹。(抽象語法樹是一個(gè)深度嵌套的對象,這個(gè)對象以一種既能夠簡單地操作又提供很多關(guān)于源代碼信息的形式,來展現(xiàn)代碼。)

          轉(zhuǎn)換

          • 這個(gè)過程接收解析生成的抽象語法樹并對它做出改動(dòng)
          • 轉(zhuǎn)換階段可以改變抽象語法樹使代碼保持在同一個(gè)語言,或者編譯成另外一門語言。

          代碼生成

          • 生成新的代碼,一般是二進(jìn)制或者匯編

          aki-DSL解釋器設(shè)計(jì)原理

          解析源代碼生成AST

          那么想要實(shí)現(xiàn)一個(gè)腳本解釋器的話,就需要實(shí)現(xiàn)上面的三個(gè)步驟,而且我們發(fā)現(xiàn),承上啟下的是AST(抽象語法樹),它在解釋器中十分重要

          好在萬能的golang將parse api暴露給用戶了,可以讓我們省去一大部分工作去做語法解析得到AST,示例代碼如下:

          package main
          
          import (
          	"fmt"
          	"go/ast"
          	"go/parser"
          	"go/token"
          )
          
          func main() {
          	expr :=`a==1 && b==2`
          	fset :=token.NewFileSet()
          	exprAst, err :=parser.ParseExpr(expr)
          	if err !=nil {
          		fmt.Println(err)
          		return
          	}
          
          	ast.Print(fset, exprAst)
          }

          得到的結(jié)果:

          0  *ast.BinaryExpr {
               1  .  X: *ast.BinaryExpr {
               2  .  .  X: *ast.Ident {
               3  .  .  .  NamePos: -
               4  .  .  .  Name: "a"
               5  .  .  .  Obj: *ast.Object {
               6  .  .  .  .  Kind: bad
               7  .  .  .  .  Name: ""
               8  .  .  .  }
               9  .  .  }
              10  .  .  OpPos: -
              11  .  .  Op:==12  .  .  Y: *ast.BasicLit {
              13  .  .  .  ValuePos: -
              14  .  .  .  Kind: INT
              15  .  .  .  Value: "1"
              16  .  .  }
              17  .  }
              18  .  OpPos: -
              19  .  Op: &&
              20  .  Y: *ast.BinaryExpr {
              21  .  .  X: *ast.Ident {
              22  .  .  .  NamePos: -
              23  .  .  .  Name: "b"
              24  .  .  .  Obj: *(obj @ 5)
              25  .  .  }
              26  .  .  OpPos: -
              27  .  .  Op:==28  .  .  Y: *ast.BasicLit {
              29  .  .  .  ValuePos: -
              30  .  .  .  Kind: INT
              31  .  .  .  Value: "2"
              32  .  .  }
              33  .  }
              34  }

          并且,作為一個(gè)嵌入式的DSL,我們的設(shè)計(jì)是依托在golang代碼之上運(yùn)行的,我們不需要代碼生成這一個(gè)步驟,直接使用golang來解析AST來執(zhí)行相應(yīng)的操作

          那么,我們的現(xiàn)在的工作就是如何解析AST并做相應(yīng)的操作即可.

          解析AST

          AST的結(jié)構(gòu)分析

          那么AST是什么結(jié)構(gòu)呢,他大致可以分為如下結(jié)構(gòu)

          1.ast.Decl

          All declaration nodes implement the Decl interface.

          var a int //GenDecl
          func main()  //FuncDecl

          2.ast.Stmt

          All statement nodes implement the Stmt interface.

          a :=1 //AssignStmt
          b :=map[string]string{"name":"nber1994", "age":"eghiteen"}
          
          if a > 2 { //IfStmt
          	b["age"]="18" //BlockStmt 
          } else {
          }
          
          for i:=0;i<10;i++ { //ForStmt
          }
          
          
          for k, v :=range b { //RangeStmt
          }
          return a //ReturnStmt

          3.ast.Expr

          All expression nodes implement the Expr interface.

          a :=1 //BasicLit
          b :="string"
          a=a + 1 //BinaryExpr
          b :=map[string]string{} //CompositLitExpr
          c :=Get("test.test") //CallExpr
          d :=b["name"] //IndexExpr

          主要思路

          通過分析AST結(jié)構(gòu)我們知道,一個(gè)ast.Decl是由多個(gè)ast.Stmt,并且一個(gè)ast.Stmt是由多個(gè)ast.Expr組成的,簡單來說就是一個(gè)樹形結(jié)構(gòu),那么這么一來就好辦了,代碼大框架一定是遞歸。

          我們自底向上,分別實(shí)現(xiàn)對各種類型的ast.Expr,ast.Stmt, ast.Decl的解釋執(zhí)行方法,并把解釋結(jié)果向上傳遞。然后通過一個(gè)根節(jié)點(diǎn)切入,遞歸方式從上向下解釋執(zhí)行即可

          主要代碼:

          //編譯Expr
          func (this *Expr) CompileExpr(dct *dslCxt.DslCxt, rct *Stmt, r ast.Expr) interface{} {
              var ret interface{}
              if nil==r {
                  return ret
              }
              switch r :=r.(type) {
              case *ast.BasicLit: //基本類型
                  ret=this.CompileBasicLitExpr(dct, rct, r)
              case *ast.BinaryExpr: //二元表達(dá)式
                  ret=this.CompileBinaryExpr(dct, rct, r)
              case *ast.CompositeLit: //集合類型
                  switch  r.Type.(type) {
                  case *ast.ArrayType: //數(shù)組
                      ret=this.CompileArrayExpr(dct, rct, r)
                  case *ast.MapType: //map
                      ret=this.CompileMapExpr(dct, rct, r)
                  default:
                      panic("syntax error: nonsupport expr type")
                  }
              case *ast.CallExpr:
                  ret=this.CompileCallExpr(dct, rct, r)
              case *ast.Ident:
                  ret=this.CompileIdentExpr(dct, rct, r)
              case *ast.IndexExpr:
                  ret=this.CompileIndexExpr(dct, rct, r)
              default:
                  panic("syntax error: nonsupport expr type")
              }
              return ret
          }
          
          //編譯stmt
          func (this *Stmt) CompileStmt(cpt *CompileCxt, stmt ast.Stmt) {
              if nil==stmt {
                  return
              }
              cStmt :=this.NewChild()
              switch stmt :=stmt.(type) {
              case *ast.AssignStmt:
                  //賦值在本節(jié)點(diǎn)的內(nèi)存中
                  this.CompileAssignStmt(cpt, stmt)
              case *ast.IncDecStmt:
                  this.CompileIncDecStmt(cpt, stmt)
              case *ast.IfStmt:
                  cStmt.CompileIfStmt(cpt, stmt)
              case *ast.ForStmt:
                  cStmt.CompileForStmt(cpt, stmt)
              case *ast.RangeStmt:
                  cStmt.CompileRangeStmt(cpt, stmt)
              case *ast.ReturnStmt:
                  cStmt.CompileReturnStmt(cpt, stmt)
              case *ast.BlockStmt:
                  cStmt.CompileBlockStmt(cpt, stmt)
              case *ast.ExprStmt:
                  cStmt.CompileExprStmt(cpt, stmt)
              default:
                  panic("syntax error: nonsupport stmt ")
              }
          }

          實(shí)現(xiàn)runtime context

          代碼的整體結(jié)構(gòu)有了,那么對于DSL中聲明的變量存儲(chǔ),以及局部變量的作用域怎么解決呢

          首先,從虛擬內(nèi)存的結(jié)構(gòu)我們得到啟發(fā),可以使用hash表的結(jié)構(gòu)來模擬最基本的內(nèi)存空間以及存取操作,得益于golang的interface{},我們可以把不同數(shù)據(jù)類型的數(shù)據(jù)存入一個(gè)map[string]interface{}中得到一個(gè)范類型的數(shù)組,這樣我們就構(gòu)建出了一個(gè)簡單的runtime memory的雛形。

          type RunCxt struct {
              Vars map[string]interface{}
              Name string
          }
          
          func NewRunCxt() *RunCxt{
              return &RunCxt{
                  Vars: make(map[string]interface{}),
              }
          }
          
          //獲取值
          func (this *RunCxt) GetValue(varName string) interface{}{
              if _, exist :=this.Vars[varName]; !exist {
                  panic("syntax error: not exist var")
              }
              return this.Vars[varName]
          }
          
          func (this *RunCxt) ValueExist(varName string) bool {
              _, exist :=this.Vars[varName]
              return exist
          }
          
          //設(shè)置值
          func (this *RunCxt) SetValue(varName string, value interface{}) bool {
              this.Vars[varName]=value
              return true
          }
          
          func (this *RunCxt) ToString() string {
              jsonStu, _ :=json.Marshal(this.Vars)
              return string(jsonStu)
          }

          那么,如何實(shí)現(xiàn)局部變量的作用域呢?

          package main
          
          func main() {
              a :=2
          
              for i:=0;i<10;i++ {
                  a++
                  b :=2
              }
              a=3
              b=3 //error b的聲明是在for語句中,外部是無法訪問的
          }

          那么,這個(gè)runtime context的位置就很重要,我們做如下處理:

          每個(gè)Stmt節(jié)點(diǎn)都有一個(gè)runtime context 寫入數(shù)據(jù)時(shí),AssignStmt類型在本Stmt節(jié)點(diǎn)中賦值,其他類型新建一個(gè)Stmt子節(jié)點(diǎn)執(zhí)行 讀取數(shù)據(jù)時(shí),從本節(jié)點(diǎn)開始向上遍歷父節(jié)點(diǎn),在runtime context中尋找變量,找到即止 通過這一機(jī)制,我們可以得到的效果是:

          同一個(gè)BlockStmt下的多個(gè)Stmt(IfStmt,F(xiàn)orStmt等)處理節(jié)點(diǎn)之間的runtime context是互相隔離的 每個(gè)子節(jié)點(diǎn),都能訪問到父節(jié)點(diǎn)中定義的變量

          代碼實(shí)現(xiàn):

          type Stmt struct{
              Rct *runCxt.RunCxt //變量作用空間
              Type int
              Father *Stmt //子節(jié)點(diǎn)可以訪問到父節(jié)點(diǎn)的內(nèi)存空間
          }
          
          func NewStmt() *Stmt {
              rct :=runCxt.NewRunCxt()
              return &Stmt{
                  Rct: rct,
              }
          }
          
          func (this *Stmt) NewChild() *Stmt {
              stmt :=NewStmt()
              stmt.Father=this
              return stmt
          }
          
          //編譯stmt
          func (this *Stmt) CompileStmt(cpt *CompileCxt, stmt ast.Stmt) {
              if nil==stmt {
                  return
              }
              cStmt :=this.NewChild()
              switch stmt :=stmt.(type) {
              case *ast.AssignStmt:
                  //賦值在本節(jié)點(diǎn)的內(nèi)存中
                  this.CompileAssignStmt(cpt, stmt)
              case *ast.IncDecStmt:
                  this.CompileIncDecStmt(cpt, stmt)
              case *ast.IfStmt:
                  cStmt.CompileIfStmt(cpt, stmt)
              case *ast.ForStmt:
                  cStmt.CompileForStmt(cpt, stmt)
              case *ast.RangeStmt:
                  cStmt.CompileRangeStmt(cpt, stmt)
              case *ast.ReturnStmt:
                  cStmt.CompileReturnStmt(cpt, stmt)
              case *ast.BlockStmt:
                  cStmt.CompileBlockStmt(cpt, stmt)
              case *ast.ExprStmt:
                  cStmt.CompileExprStmt(cpt, stmt)
              default:
                  panic("syntax error: nonsupport stmt ")
              }
          }

          變量類型與內(nèi)部變量類型

          首先,嵌入式的是golang系統(tǒng),為了和外部系統(tǒng)保持一個(gè)很好地?cái)?shù)據(jù)類型交互以及數(shù)據(jù)的準(zhǔn)確性,DSL最好也是強(qiáng)類型語言。但是為了簡單,我們會(huì)刪減一些數(shù)據(jù)類型,保留最基本且最穩(wěn)定的數(shù)據(jù)類型

          func (this *Expr) CompileBasicLitExpr(cpt *CompileCxt, rct *Stmt, r *ast.BasicLit) interface{} {
              var ret interface{}
              switch r.Kind {
              case token.INT:
                  ret=cast.ToInt64(r.Value)
              case token.FLOAT:
                  ret=cast.ToFloat64(r.Value)
              case token.STRING:
                  retStr :=cast.ToString(r.Value)
                  var err error
                  ret, err=strconv.Unquote(retStr)
                  if nil !=err {
                      panic(fmt.Sprintf("syntax error: Bad String %v", cpt.Fset.Position(r.Pos())))
                  }
              default:
                  panic(fmt.Sprintf("syntax error: Bad BasicList Type %v", cpt.Fset.Position(r.Pos())))
              }
              return ret
          }
          
          func (this *Expr) CompileMapExpr(cpt *CompileCxt, rct *Stmt, r *ast.CompositeLit) interface{} {
              ret :=make(map[interface{}]interface{})
              var key interface{}
              var value interface{}
              for _, e :=range r.Elts {
                  key=this.CompileExpr(cpt, rct, e.(*ast.KeyValueExpr).Key)
                  value=this.CompileExpr(cpt, rct, e.(*ast.KeyValueExpr).Value)
                  ret[key]=value
              }
              return ret
          }
          
          func (this *Expr) CompileArrayExpr(cpt *CompileCxt, rct *Stmt, r *ast.CompositeLit) interface{} {
              var ret []interface{}
              for _, e :=range r.Elts {
                  switch e :=e.(type) {
                  case *ast.BasicLit:
                      ret=append(ret, this.CompileExpr(cpt, rct, e))
                  case *ast.CompositeLit:
                      //拼接結(jié)構(gòu)體
                      compLit :=*.CompositeLit{
                          Type: r.Type.(*ast.ArrayType).Elt,
                          Elts: e.Elts,
                      }
                      ret=append(ret, this.CompileExpr(cpt, rct, compLit))
                  default:
                      panic(fmt.Sprintf("syntax error: Bad Array Item Type %v", cpt.Fset.Position(r.Pos())))
                  }
              }
              return ret
          }

          我們可以看到,DSL數(shù)據(jù)與go數(shù)據(jù)類型對應(yīng)關(guān)系為:

          DSL數(shù)據(jù)類型go數(shù)據(jù)類型備注intint64最大范圍floatfloat64最大范圍stringstringmapmap[interface{}]interface{}最大容忍度array slice[]interface{}{}最大容忍度

          DSL與外部系統(tǒng)交互

          通過JsonMap與外部系統(tǒng)進(jìn)行交互,且提供Get(path) Set(path)方法,去動(dòng)態(tài)的訪問與修改Json context中的節(jié)點(diǎn)

          但是外部交互Json又是多種結(jié)構(gòu)類型的,借助于nodejson可以解析動(dòng)態(tài)json結(jié)構(gòu),通過XX.X格式的路徑,來動(dòng)態(tài)的訪問和修改json中的字段

          解析CallExpr,通過reflect來調(diào)用內(nèi)部函數(shù)

          func (this *Expr) CompileCallExpr(dct *dslCxt.DslCxt, rct *Stmt, r *ast.CallExpr) interface{} {
              var ret interface{}
              //校驗(yàn)內(nèi)置函數(shù)
              var funcArgs []reflect.Value
              funcName :=r.Fun.(*ast.Ident).Name
              //初始化入?yún)?
              for _, arg :=range r.Args {
                  funcArgs=append(funcArgs, reflect.ValueOf(this.CompileExpr(dct, rct, arg)))
              }
              var res []reflect.Value
              if RealFuncName, exist:=SupFuncList[funcName]; exist {
                  flib :=NewFuncLib()
                  res=reflect.ValueOf(flib).MethodByName(RealFuncName).Call(funcArgs)
              } else {
                  res=reflect.ValueOf(dct).MethodByName(funcName).Call(funcArgs)
              }
              if nil==res {
                  return ret
              }
              return res[0].Interface()
          }

          成果

          https://github.com/nber1994/akiDsl

          Testcontainers for Go使開發(fā)人員能夠輕松地針對容器化依賴項(xiàng)運(yùn)行測試。在我們之前的文章中,您可以找到使用 Testcontainers 進(jìn)行集成測試的介紹,并探索如何使用 Testcontainers(用 Java)編寫功能測試。

          這篇博文將深入探討如何使用模塊以及 Golang 測試容器的常見問題。

          我們用它做什么?

          服務(wù)經(jīng)常使用外部依賴項(xiàng),如數(shù)據(jù)存儲(chǔ)或隊(duì)列。可以模擬這些依賴項(xiàng),但如果您想要運(yùn)行集成測試,最好根據(jù)實(shí)際依賴項(xiàng)(或足夠接近)進(jìn)行驗(yàn)證。

          使用依賴項(xiàng)的映像啟動(dòng)容器是驗(yàn)證應(yīng)用程序是否按預(yù)期運(yùn)行的便捷方法。使用 Testcontainers,啟動(dòng)容器是通過編程方式完成的,因此您可以將其定義為測試的一部分。運(yùn)行測試的機(jī)器(開發(fā)人員、CI/CD)需要具有容器運(yùn)行時(shí)接口(例如 Docker、Podman...)

          基本實(shí)現(xiàn)

          Testcontainers for Go 非常易于使用,快速啟動(dòng)示例如下:

          ctx :=context.TODO()
          req :=testcontainers.ContainerRequest{
              Image:        "redis:latest",
              ExposedPorts: []string{"6379/tcp"},
              WaitingFor:   wait.ForLog("Ready to accept connections"),
          }
          redisC, err :=testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
              ContainerRequest: req,
              Started:          true,
          })
          if err !=nil {
              panic(err)
          }
          defer func() {
              if err :=redisC.Terminate(ctx); err !=nil {
                  panic(err)
              }
          }()
          

          如果我們深入研究上面的代碼,我們會(huì)注意到:

          1. testcontainers.ContainerRequest使用容器鏡像、暴露端口和等待策略參數(shù)初始化結(jié)構(gòu)體
          2. testcontainers.GenericContainer啟動(dòng)容器并返回容器和錯(cuò)誤結(jié)構(gòu)
          3. redisC.Terminatedefer測試完成后終止容器

          實(shí)現(xiàn)我們自己的內(nèi)部庫

          從上一節(jié)的例子來看,存在一些小小的不便:

          1. wait.ForLog("Ready to accept connections")使用日志等待容器啟動(dòng),這很容易中斷
          2. ExposedPorts: []string{"6379/tcp"}需要了解 Redis 的暴露端口

          運(yùn)行 Redis 容器可能還需要一些額外的環(huán)境變量和其他參數(shù),這需要更深入的知識(shí)。因此,我們決定創(chuàng)建一個(gè)內(nèi)部庫,該庫將使用簡化測試實(shí)施所需的默認(rèn)參數(shù)初始化容器。為了保持靈活性,我們使用了功能選項(xiàng)模式,以便消費(fèi)者仍然可以根據(jù)需要進(jìn)行自定義。

          Redis 的實(shí)現(xiàn)示例:

          func defaultPreset() []container.Option {
              return []container.Option{
                  container.WithPort("6379/tcp"),
                  container.WithGetURL(func(port nat.Port) string {
                      return "localhost:" + port.Port()
                  }),
                  container.WithImage("redis"),
                  container.WithWaitingStrategy(func(c *container.Container) wait.Strategy {
                      return wait.ForAll(
                          wait.NewHostPortStrategy(c.Port),
                          wait.ForLog("Ready to accept connections"))
                  }),
              }
          }
          
          // New - create a new container able to run redis
          func New(options ...container.Option) (*container.Container, error) {
              c :=container.Container{}
              options=append(defaultPreset(), options...)
              for _, o :=range options {
                  o(&c)
              }
          
              return &c, nil
          }
          
          // Start - start a Redis container and return a container.CreatedContainer
          func Start(ctx context.Context, options ...container.Option) (container.CreatedContainer, error) {
              p, err :=New(options...)
              if err !=nil {
                  return container.CreatedContainer{}, err
              }
              return p.Start(ctx)
          }
          

          Redis 庫的使用:

          ctx :=context.TODO()
          cc, err :=redis.Start(ctx, container.WithVersion("latest"))
          if err !=nil {
              panic(err)
          }
          defer func() {
              if err :=cc.Stop(ctx, nil); err !=nil {
                  panic(err)
              }
          }()
          

          有了這個(gè)內(nèi)部庫,開發(fā)人員可以輕松地為 Redis 添加測試,而無需弄清楚等待策略、暴露端口等。如果出現(xiàn)不兼容的情況,可以更新內(nèi)部庫以集中修復(fù)問題。

          常見問題 - 垃圾收集器(Ryuk / Reaper)

          Testcontainers 還額外確保了測試完成后容器會(huì)被移除,它使用垃圾收集器defer,這是一個(gè)作為“sidecar”啟動(dòng)的附加容器。即使測試崩潰(這將阻止運(yùn)行),此容器也會(huì)負(fù)責(zé)停止正在測試的容器。

          當(dāng)使用Docker時(shí),它可以正常工作,但使用其他容器運(yùn)行時(shí)接口(如Podman)時(shí)經(jīng)常會(huì)遇到這種錯(cuò)誤:Error response from daemon: container create: statfs /var/run/docker.sock: permission denied: creating reaper failed: failed to create container

          “修復(fù)此問題”的一種方法是使用環(huán)境變量將其停用TESTCONTAINERS_RYUK_DISABLED=true

          另一種方法是設(shè)置 Podman 機(jī)器 rootful 并添加:

          export TESTCONTAINERS_RYUK_CONTAINER_PRIVILEGED=true; # needed to run Reaper (alternative disable it TESTCONTAINERS_RYUK_DISABLED=true)
          export TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=/var/run/docker.sock; # needed to apply the bind with statfs
          

          在我們的內(nèi)部庫中,我們采取默認(rèn)禁用它的方法,因?yàn)殚_發(fā)人員在本地運(yùn)行它時(shí)遇到了問題。

          轉(zhuǎn)向模塊

          一旦我們的內(nèi)部庫足夠穩(wěn)定,我們就決定是時(shí)候通過為 Testcontainers 做貢獻(xiàn)來回饋社區(qū)了。但令人驚訝的是…… Testcontainers 剛剛引入了模塊。模塊的功能與我們的內(nèi)部庫完全一樣,因此我們將所有服務(wù)遷移到模塊并停止使用內(nèi)部庫。從遷移中,我們了解到,既然已經(jīng)引入了模塊,就可以使用開箱即用的標(biāo)準(zhǔn)庫,從而降低了我們服務(wù)的維護(hù)成本。主要的挑戰(zhàn)是使用 Makefile 微調(diào)開發(fā)人員環(huán)境變量以在開發(fā)人員機(jī)器上運(yùn)行(使垃圾收集器工作)。

          改編自testcontainers 文檔的示例:

          ctx :=context.TODO()
          redisContainer, err :=redis.RunContainer(ctx,
              testcontainers.WithImage("docker.io/redis:latest"),
          )
          if err !=nil {
              panic(err)
          }
          defer func() {
              if err :=redisContainer.Terminate(ctx); err !=nil {
                  panic(err)
              }
          }()
          

          結(jié)論

          Testcontainers for Golang 是一個(gè)很棒的支持測試的庫,現(xiàn)在引入了模塊,它變得更好了。垃圾收集器存在一些小障礙,但可以按照本文所述輕松修復(fù)。

          我希望通過這個(gè)博客,如果您還沒有采用 Testcontainers,我們強(qiáng)烈推薦它來提高您的應(yīng)用程序的可測試性。


          作者:Fabien Pozzobon

          出處:https://engineering.zalando.com/posts/2023/12/using-modules-for-testcontainers-with-golang.html

          次聊到了《Go語言進(jìn)階之路(八):正則表達(dá)式


          主站蜘蛛池模板: 婷婷亚洲综合一区二区| 日韩一区二区在线播放| 精品伦精品一区二区三区视频 | 亚洲成AV人片一区二区| 一区二区国产精品| 无码午夜人妻一区二区三区不卡视频 | 久久人妻内射无码一区三区| 国产日韩一区二区三区在线观看| 亚洲国产精品乱码一区二区| 国产精品一区在线播放| 一区二区三区午夜| 亚洲一区二区三区高清视频| 亚洲电影一区二区三区| 在线观看日本亚洲一区| 一区五十路在线中出| 亚洲国产一区在线观看| 国产观看精品一区二区三区| 人妻AV中文字幕一区二区三区| 国产激情一区二区三区在线观看 | 亚洲一区二区三区亚瑟| 国产成人av一区二区三区不卡| 久久久久一区二区三区| 一区 二区 三区 中文字幕| 伊人色综合视频一区二区三区| www一区二区三区| 日本一区二区三区中文字幕| 亚洲日本久久一区二区va| 国产成人一区二区三区在线| 四虎在线观看一区二区 | 韩国理伦片一区二区三区在线播放 | 国产精品亚洲综合一区在线观看| 一区二区三区日韩| 亚洲国产精品一区二区第一页| 久久无码一区二区三区少妇| 日韩视频在线观看一区二区| 国产麻豆媒一区一区二区三区| 日韩最新视频一区二区三| 亚洲一区电影在线观看| 亚洲国产美国国产综合一区二区| 一区二区三区在线| 久久国产精品免费一区|