2024/8/15
laprasdrum.icon 4時起き🌞
/icons/hr.icon
Swift Testingのinit/deinitの実行タイミング
事前にparallel executionの設定は切ってる。
https://scrapbox.io/files/66bd1dc32937ac001cecbac6.png
検証用のテストコードは以下。
code:Sample.swift
class SampleTest() {
init() async throws {
print(#function)
}
deinit {
print(#function)
}
@Test func test1()
print("start: \(#function)")
print("end: \(#function)")
}
@Test func test2()
print("start: \(#function)")
print("end: \(#function)")
}
@Test func test3()
print("start: \(#function)")
print("end: \(#function)")
}
@Test func test4()
print("start: \(#function)")
print("end: \(#function)")
}
}
以下ログ。
code:out.log
init()
init()
init()
init()
start: test1()
start: test2()
start: test3()
start: test4()
end: test1()
deinit
end: test2()
deinit
end: test4()
deinit
end: test3()
deinit
理想はXCTestCaseのsetUp/tearDownのように
code:expected.log
init()
start: test1()
end()
deinit
init()
start: test2()
end: test2()
deinit
...
を期待していたがそうではなかった。
なのでaddTearDownBlock以前にsetUp/tearDown相当の挙動を期待するなら @Test が生成するテスト用のモデルにsetUp/tearDown blockを挿入するようなプラグインが必要になりそう。
実現可能かどうかも含めてコードリーディングを進める。
BitriseのRestore SPM cache stepの実装を読む
Restore SPM cacheがたまに遅いときがあるので、Forumで聞く前に実装を読むことにした。
比較としてSPM限定ではないRestore cache stepの実装も並べておく。
2つとも Run() 内で cache.NewRestorer(...).Restore(...) していることに変わりなし。
Restore() の実装は bitrise-io/go-steputils のこちら。 code:restore.go
func (r *restorer) Restore(input RestoreCacheInput) error {
config, err := r.createConfig(input)
if err != nil {
return fmt.Errorf("failed to parse inputs: %w", err)
}
tracker := newStepTracker(input.StepId, r.envRepo, r.logger)
defer tracker.wait()
r.logger.Println()
r.logger.Infof("Downloading archive...")
downloadStartTime := time.Now()
// Download a cache
result, err := r.download(context.Background(), config)
if err != nil {
if errors.Is(err, network.ErrCacheNotFound) {
r.logger.Donef("No cache entry found for the provided key")
tracker.logRestoreResult(false, "", config.Keys)
exporter := export.NewExporter(r.cmdFactory)
return exporter.ExportOutput(cacheHitEnvVar, "false")
}
return fmt.Errorf("download failed: %w", err)
}
if result.matchedKey == config.Keys0 { r.logger.Printf("Exact hit for first key")
} else {
r.logger.Printf("Cache hit for key: %s", result.matchedKey)
}
fileInfo, err := os.Stat(result.filePath)
if err != nil {
return err
}
r.logger.Printf("Archive size: %s", units.HumanSizeWithPrecision(float64(fileInfo.Size()), 3))
downloadTime := time.Since(downloadStartTime).Round(time.Second)
r.logger.Donef("Downloaded archive in %s", downloadTime)
tracker.logArchiveDownloaded(downloadTime, fileInfo, len(config.Keys))
r.logger.Println()
r.logger.Infof("Restoring archive...")
extractionStartTime := time.Now()
// Decompress a cache via an archiver
archiver := compression.NewArchiver(
r.logger,
r.envRepo,
compression.NewDependencyChecker(r.logger, r.envRepo))
if err := archiver.Decompress(result.filePath, ""); err != nil {
return fmt.Errorf("failed to decompress cache archive: %w", err)
}
extractionTime := time.Since(extractionStartTime).Round(time.Second)
r.logger.Donef("Restored archive in %s", extractionTime)
tracker.logArchiveExtracted(extractionTime, len(config.Keys))
err = r.exposeCacheHit(result, config.Keys)
if err != nil {
return err
}
tracker.logRestoreResult(true, result.matchedKey, config.Keys)
return nil
}
実態は httpClient の関数を呼び出してる。
code:api.go
type apiClient struct {
httpClient *retryablehttp.Client
baseURL string
accessToken string
logger log.Logger
}
retryablehttp.Client は hashicorp/go-retryablehttp からimportしてる。
どうやら HTTPClient がさらなる実態らしい。
code:client.go
func NewClient() *Client {
return &Client{
HTTPClient: cleanhttp.DefaultPooledClient(),
Logger: defaultLogger,
RetryWaitMin: defaultRetryWaitMin,
RetryWaitMax: defaultRetryWaitMax,
RetryMax: defaultRetryMax,
CheckRetry: DefaultRetryPolicy,
Backoff: DefaultBackoff,
}
}
cleanhttp は hashicorp/go-cleanhttp からimportしてる。
> クライアントのトランスポートには通常、内部状態 (キャッシュされた TCP 接続) があるため、必要に応じてクライアントを作成するのではなく、再利用する必要があります。クライアントは、複数の goroutine による同時使用でも安全です。
残念ながら、これは共有値であり、ライブラリが自由に変更できると想定することは珍しくありません。十分な依存関係がある場合、ライブラリとゴルーチン間でこの共有値を操作すると、奇妙な問題や競合状態が発生する可能性が非常に高くなります (クライアントは同時使用に対して安全ですが、クライアント構造体自体に値を書き込むことは保護されていません)。
このリポジトリは、Go 標準ライブラリと同じデフォルト値を使用しながら、他のクライアントと状態を共有しないクライアントを返す「クリーンな http.Client」を取得するためのいくつかの簡単な関数を提供します。
ここまで読んでDownloaderの実装に何かしら問題があるようには感じなかった。
ざっと見たけど怪しいところは特になさそう?
単純にCDNからのネットワーク速度問題なのか…もう少し情報が集まったら追記する。