2021-09-02 09:48:11 +00:00
/ *
Copyright 2021 Qiniu Cloud ( qiniu . com )
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
2021-07-22 11:57:56 +00:00
package e2e
import (
"fmt"
"log"
2021-07-23 07:25:03 +00:00
"net"
2021-07-22 11:57:56 +00:00
"os"
"path/filepath"
"regexp"
2021-07-23 07:25:03 +00:00
"strconv"
2021-07-22 11:57:56 +00:00
. "github.com/onsi/ginkgo"
2021-07-23 07:25:03 +00:00
"github.com/gofrs/flock"
2021-07-22 11:57:56 +00:00
"github.com/tongjingran/copy"
"gopkg.in/yaml.v3"
)
type Sample struct {
Dir string ` yaml:"dir" `
Description string ` yaml:"description" `
}
// SamplesMgr create and return sample for test case
//
2021-07-23 07:25:03 +00:00
// ginkgo 的并发执行时的运行模型是多进程模型,会有多个独立的 go test 进程。
2021-07-22 11:57:56 +00:00
// Ginkgo has support for running specs in parallel. It does this by spawning separate go test processes and serving specs to each process off of a shared queue.
2021-07-23 07:25:03 +00:00
//
// 所以这里设计成每个 test case 在各自的临时目录中生成 sample, 以便将来测试用例可以并发执行。
2021-07-22 11:57:56 +00:00
type SamplesMgr struct {
Samples map [ string ] Sample ` yaml:"samples" `
path string ` yaml:"-" `
}
func NewSamplesMgr ( ) * SamplesMgr {
path , _ := os . Getwd ( )
metaData , err := os . ReadFile ( filepath . Join ( path , "samples" , "meta.yaml" ) )
if err != nil {
log . Fatalf ( "fail to read sample meta" )
}
mgr := SamplesMgr { }
err = yaml . Unmarshal ( metaData , & mgr )
if err != nil {
log . Fatalf ( "fail to parse the meta yaml" )
}
mgr . path = path
return & mgr
}
// GetSampleByKey return the sample folder location for test use
func ( m * SamplesMgr ) GetSampleByKey ( key string ) ( string , error ) {
sample , ok := m . Samples [ key ]
if ! ok {
return "" , fmt . Errorf ( "no sample found" )
}
desc := CurrentGinkgoTestDescription ( )
caseTitle := desc . FullTestText + " " + key
m1 := regexp . MustCompile ( ` [/\\?%*:|"<>] ` )
caseTitle = m1 . ReplaceAllString ( caseTitle , "-" )
dst := filepath . Join ( m . path , "tmp" , caseTitle )
err := os . RemoveAll ( dst )
if err != nil {
log . Fatalf ( "fail to clean the temp sample: %v" , err )
}
err = os . MkdirAll ( dst , os . ModePerm )
if err != nil {
log . Fatalf ( "fail to create sample dir: %v" , err )
}
src := filepath . Join ( m . path , "samples" , sample . Dir )
err = copy . Copy ( src , dst )
if err != nil {
log . Fatalf ( "fail to copy the sample project: %v" , err )
}
return dst , nil
}
2021-07-23 07:25:03 +00:00
// GetAvailablePort get next available host port among multiprocess ginkgo test cases
//
// 利用文件锁和端口探活,获取下一个可用的 port
func ( m * SamplesMgr ) GetAvailablePort ( ) ( string , error ) {
fileLockPath := filepath . Join ( m . path , "tmp" , "port.lock" )
// 文件锁, ginkgo parallel 模式是多进程,必须用跨平台跨进程的同步方式
lock := flock . New ( fileLockPath )
lock . Lock ( )
defer lock . Unlock ( )
// 该文件记录 counter, 代表下一个可用的 port
portFilePath := filepath . Join ( m . path , "tmp" , "port.record" )
data , err := os . ReadFile ( portFilePath )
if err != nil {
os . Create ( portFilePath )
}
port , err := strconv . Atoi ( string ( data ) )
if err != nil {
port = 7777
} else {
port += 1
}
// 循环检测直到找到可用 port
for {
if port == 65534 {
port = 7777
}
conn , err := net . Dial ( "tcp" , ":" + strconv . Itoa ( port ) )
if err == nil {
port += 1
conn . Close ( )
} else {
break
}
}
err = os . WriteFile ( portFilePath , [ ] byte ( strconv . Itoa ( port ) ) , os . ModePerm )
return "127.0.0.1:" + strconv . Itoa ( port ) , err
}