1
0

rotatelogger_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635
  1. package logx
  2. import (
  3. "errors"
  4. "io"
  5. "os"
  6. "path"
  7. "path/filepath"
  8. "sync/atomic"
  9. "syscall"
  10. "testing"
  11. "time"
  12. "github.com/stretchr/testify/assert"
  13. "github.com/wuntsong-org/go-zero-plus/core/fs"
  14. "github.com/wuntsong-org/go-zero-plus/core/stringx"
  15. )
  16. func TestDailyRotateRuleMarkRotated(t *testing.T) {
  17. t.Run("daily rule", func(t *testing.T) {
  18. var rule DailyRotateRule
  19. rule.MarkRotated()
  20. assert.Equal(t, getNowDate(), rule.rotatedTime)
  21. })
  22. t.Run("daily rule", func(t *testing.T) {
  23. rule := DefaultRotateRule("test", "-", 1, false)
  24. _, ok := rule.(*DailyRotateRule)
  25. assert.True(t, ok)
  26. })
  27. }
  28. func TestDailyRotateRuleOutdatedFiles(t *testing.T) {
  29. t.Run("no files", func(t *testing.T) {
  30. var rule DailyRotateRule
  31. assert.Empty(t, rule.OutdatedFiles())
  32. rule.days = 1
  33. assert.Empty(t, rule.OutdatedFiles())
  34. rule.gzip = true
  35. assert.Empty(t, rule.OutdatedFiles())
  36. })
  37. t.Run("bad files", func(t *testing.T) {
  38. rule := DailyRotateRule{
  39. filename: "[a-z",
  40. }
  41. assert.Empty(t, rule.OutdatedFiles())
  42. rule.days = 1
  43. assert.Empty(t, rule.OutdatedFiles())
  44. rule.gzip = true
  45. assert.Empty(t, rule.OutdatedFiles())
  46. })
  47. t.Run("temp files", func(t *testing.T) {
  48. boundary := time.Now().Add(-time.Hour * time.Duration(hoursPerDay) * 2).Format(dateFormat)
  49. f1, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary)
  50. assert.NoError(t, err)
  51. _ = f1.Close()
  52. f2, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary)
  53. assert.NoError(t, err)
  54. _ = f2.Close()
  55. t.Cleanup(func() {
  56. _ = os.Remove(f1.Name())
  57. _ = os.Remove(f2.Name())
  58. })
  59. rule := DailyRotateRule{
  60. filename: path.Join(os.TempDir(), "go-zero-test-"),
  61. days: 1,
  62. }
  63. assert.NotEmpty(t, rule.OutdatedFiles())
  64. })
  65. }
  66. func TestDailyRotateRuleShallRotate(t *testing.T) {
  67. var rule DailyRotateRule
  68. rule.rotatedTime = time.Now().Add(time.Hour * 24).Format(dateFormat)
  69. assert.True(t, rule.ShallRotate(0))
  70. }
  71. func TestSizeLimitRotateRuleMarkRotated(t *testing.T) {
  72. t.Run("size limit rule", func(t *testing.T) {
  73. var rule SizeLimitRotateRule
  74. rule.MarkRotated()
  75. assert.Equal(t, getNowDateInRFC3339Format(), rule.rotatedTime)
  76. })
  77. t.Run("size limit rule", func(t *testing.T) {
  78. rule := NewSizeLimitRotateRule("foo", "-", 1, 1, 1, false)
  79. rule.MarkRotated()
  80. assert.Equal(t, getNowDateInRFC3339Format(), rule.(*SizeLimitRotateRule).rotatedTime)
  81. })
  82. }
  83. func TestSizeLimitRotateRuleOutdatedFiles(t *testing.T) {
  84. t.Run("no files", func(t *testing.T) {
  85. var rule SizeLimitRotateRule
  86. assert.Empty(t, rule.OutdatedFiles())
  87. rule.days = 1
  88. assert.Empty(t, rule.OutdatedFiles())
  89. rule.gzip = true
  90. assert.Empty(t, rule.OutdatedFiles())
  91. rule.maxBackups = 0
  92. assert.Empty(t, rule.OutdatedFiles())
  93. })
  94. t.Run("bad files", func(t *testing.T) {
  95. rule := SizeLimitRotateRule{
  96. DailyRotateRule: DailyRotateRule{
  97. filename: "[a-z",
  98. },
  99. }
  100. assert.Empty(t, rule.OutdatedFiles())
  101. rule.days = 1
  102. assert.Empty(t, rule.OutdatedFiles())
  103. rule.gzip = true
  104. assert.Empty(t, rule.OutdatedFiles())
  105. })
  106. t.Run("temp files", func(t *testing.T) {
  107. boundary := time.Now().Add(-time.Hour * time.Duration(hoursPerDay) * 2).Format(dateFormat)
  108. f1, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary)
  109. assert.NoError(t, err)
  110. f2, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary)
  111. assert.NoError(t, err)
  112. boundary1 := time.Now().Add(time.Hour * time.Duration(hoursPerDay) * 2).Format(dateFormat)
  113. f3, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary1)
  114. assert.NoError(t, err)
  115. t.Cleanup(func() {
  116. _ = f1.Close()
  117. _ = os.Remove(f1.Name())
  118. _ = f2.Close()
  119. _ = os.Remove(f2.Name())
  120. _ = f3.Close()
  121. _ = os.Remove(f3.Name())
  122. })
  123. rule := SizeLimitRotateRule{
  124. DailyRotateRule: DailyRotateRule{
  125. filename: path.Join(os.TempDir(), "go-zero-test-"),
  126. days: 1,
  127. },
  128. maxBackups: 3,
  129. }
  130. assert.NotEmpty(t, rule.OutdatedFiles())
  131. })
  132. t.Run("no backups", func(t *testing.T) {
  133. boundary := time.Now().Add(-time.Hour * time.Duration(hoursPerDay) * 2).Format(dateFormat)
  134. f1, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary)
  135. assert.NoError(t, err)
  136. f2, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary)
  137. assert.NoError(t, err)
  138. boundary1 := time.Now().Add(time.Hour * time.Duration(hoursPerDay) * 2).Format(dateFormat)
  139. f3, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary1)
  140. assert.NoError(t, err)
  141. t.Cleanup(func() {
  142. _ = f1.Close()
  143. _ = os.Remove(f1.Name())
  144. _ = f2.Close()
  145. _ = os.Remove(f2.Name())
  146. _ = f3.Close()
  147. _ = os.Remove(f3.Name())
  148. })
  149. rule := SizeLimitRotateRule{
  150. DailyRotateRule: DailyRotateRule{
  151. filename: path.Join(os.TempDir(), "go-zero-test-"),
  152. days: 1,
  153. },
  154. }
  155. assert.NotEmpty(t, rule.OutdatedFiles())
  156. logger := new(RotateLogger)
  157. logger.rule = &rule
  158. logger.maybeDeleteOutdatedFiles()
  159. assert.Empty(t, rule.OutdatedFiles())
  160. })
  161. }
  162. func TestSizeLimitRotateRuleShallRotate(t *testing.T) {
  163. var rule SizeLimitRotateRule
  164. rule.rotatedTime = time.Now().Add(time.Hour * 24).Format(fileTimeFormat)
  165. rule.maxSize = 0
  166. assert.False(t, rule.ShallRotate(0))
  167. rule.maxSize = 100
  168. assert.False(t, rule.ShallRotate(0))
  169. assert.True(t, rule.ShallRotate(101*megaBytes))
  170. }
  171. func TestRotateLoggerClose(t *testing.T) {
  172. t.Run("close", func(t *testing.T) {
  173. filename, err := fs.TempFilenameWithText("foo")
  174. assert.Nil(t, err)
  175. if len(filename) > 0 {
  176. defer os.Remove(filename)
  177. }
  178. logger, err := NewLogger(filename, new(DailyRotateRule), false)
  179. assert.Nil(t, err)
  180. _, err = logger.Write([]byte("foo"))
  181. assert.Nil(t, err)
  182. assert.Nil(t, logger.Close())
  183. })
  184. t.Run("close and write", func(t *testing.T) {
  185. logger := new(RotateLogger)
  186. logger.done = make(chan struct{})
  187. close(logger.done)
  188. _, err := logger.Write([]byte("foo"))
  189. assert.ErrorIs(t, err, ErrLogFileClosed)
  190. })
  191. t.Run("close without losing logs", func(t *testing.T) {
  192. text := "foo"
  193. filename, err := fs.TempFilenameWithText(text)
  194. assert.Nil(t, err)
  195. if len(filename) > 0 {
  196. defer os.Remove(filename)
  197. }
  198. logger, err := NewLogger(filename, new(DailyRotateRule), false)
  199. assert.Nil(t, err)
  200. msg := []byte("foo")
  201. n := 100
  202. for i := 0; i < n; i++ {
  203. _, err = logger.Write(msg)
  204. assert.Nil(t, err)
  205. }
  206. assert.Nil(t, logger.Close())
  207. bs, err := os.ReadFile(filename)
  208. assert.Nil(t, err)
  209. assert.Equal(t, len(msg)*n+len(text), len(bs))
  210. })
  211. }
  212. func TestRotateLoggerGetBackupFilename(t *testing.T) {
  213. filename, err := fs.TempFilenameWithText("foo")
  214. assert.Nil(t, err)
  215. if len(filename) > 0 {
  216. defer os.Remove(filename)
  217. }
  218. logger, err := NewLogger(filename, new(DailyRotateRule), false)
  219. assert.Nil(t, err)
  220. assert.True(t, len(logger.getBackupFilename()) > 0)
  221. logger.backup = ""
  222. assert.True(t, len(logger.getBackupFilename()) > 0)
  223. }
  224. func TestRotateLoggerMayCompressFile(t *testing.T) {
  225. old := os.Stdout
  226. os.Stdout = os.NewFile(0, os.DevNull)
  227. defer func() {
  228. os.Stdout = old
  229. }()
  230. filename, err := fs.TempFilenameWithText("foo")
  231. assert.Nil(t, err)
  232. if len(filename) > 0 {
  233. defer os.Remove(filename)
  234. }
  235. logger, err := NewLogger(filename, new(DailyRotateRule), false)
  236. assert.Nil(t, err)
  237. logger.maybeCompressFile(filename)
  238. _, err = os.Stat(filename)
  239. assert.Nil(t, err)
  240. }
  241. func TestRotateLoggerMayCompressFileTrue(t *testing.T) {
  242. old := os.Stdout
  243. os.Stdout = os.NewFile(0, os.DevNull)
  244. defer func() {
  245. os.Stdout = old
  246. }()
  247. filename, err := fs.TempFilenameWithText("foo")
  248. assert.Nil(t, err)
  249. logger, err := NewLogger(filename, new(DailyRotateRule), true)
  250. assert.Nil(t, err)
  251. if len(filename) > 0 {
  252. defer os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
  253. }
  254. logger.maybeCompressFile(filename)
  255. _, err = os.Stat(filename)
  256. assert.NotNil(t, err)
  257. }
  258. func TestRotateLoggerRotate(t *testing.T) {
  259. filename, err := fs.TempFilenameWithText("foo")
  260. assert.Nil(t, err)
  261. logger, err := NewLogger(filename, new(DailyRotateRule), true)
  262. assert.Nil(t, err)
  263. if len(filename) > 0 {
  264. defer func() {
  265. os.Remove(logger.getBackupFilename())
  266. os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
  267. }()
  268. }
  269. err = logger.rotate()
  270. switch v := err.(type) {
  271. case *os.LinkError:
  272. // avoid rename error on docker container
  273. assert.Equal(t, syscall.EXDEV, v.Err)
  274. case *os.PathError:
  275. // ignore remove error for tests,
  276. // files are cleaned in GitHub actions.
  277. assert.Equal(t, "remove", v.Op)
  278. default:
  279. assert.Nil(t, err)
  280. }
  281. }
  282. func TestRotateLoggerWrite(t *testing.T) {
  283. filename, err := fs.TempFilenameWithText("foo")
  284. assert.Nil(t, err)
  285. rule := new(DailyRotateRule)
  286. logger, err := NewLogger(filename, rule, true)
  287. assert.Nil(t, err)
  288. if len(filename) > 0 {
  289. defer func() {
  290. os.Remove(logger.getBackupFilename())
  291. os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
  292. }()
  293. }
  294. // the following write calls cannot be changed to Write, because of DATA RACE.
  295. logger.write([]byte(`foo`))
  296. rule.rotatedTime = time.Now().Add(-time.Hour * 24).Format(dateFormat)
  297. logger.write([]byte(`bar`))
  298. logger.Close()
  299. logger.write([]byte(`baz`))
  300. }
  301. func TestLogWriterClose(t *testing.T) {
  302. assert.Nil(t, newLogWriter(nil).Close())
  303. }
  304. func TestRotateLoggerWithSizeLimitRotateRuleClose(t *testing.T) {
  305. filename, err := fs.TempFilenameWithText("foo")
  306. assert.Nil(t, err)
  307. if len(filename) > 0 {
  308. defer os.Remove(filename)
  309. }
  310. logger, err := NewLogger(filename, new(SizeLimitRotateRule), false)
  311. assert.Nil(t, err)
  312. _ = logger.Close()
  313. }
  314. func TestRotateLoggerGetBackupWithSizeLimitRotateRuleFilename(t *testing.T) {
  315. filename, err := fs.TempFilenameWithText("foo")
  316. assert.Nil(t, err)
  317. if len(filename) > 0 {
  318. defer os.Remove(filename)
  319. }
  320. logger, err := NewLogger(filename, new(SizeLimitRotateRule), false)
  321. assert.Nil(t, err)
  322. assert.True(t, len(logger.getBackupFilename()) > 0)
  323. logger.backup = ""
  324. assert.True(t, len(logger.getBackupFilename()) > 0)
  325. }
  326. func TestRotateLoggerWithSizeLimitRotateRuleMayCompressFile(t *testing.T) {
  327. old := os.Stdout
  328. os.Stdout = os.NewFile(0, os.DevNull)
  329. defer func() {
  330. os.Stdout = old
  331. }()
  332. filename, err := fs.TempFilenameWithText("foo")
  333. assert.Nil(t, err)
  334. if len(filename) > 0 {
  335. defer os.Remove(filename)
  336. }
  337. logger, err := NewLogger(filename, new(SizeLimitRotateRule), false)
  338. assert.Nil(t, err)
  339. logger.maybeCompressFile(filename)
  340. _, err = os.Stat(filename)
  341. assert.Nil(t, err)
  342. }
  343. func TestRotateLoggerWithSizeLimitRotateRuleMayCompressFileTrue(t *testing.T) {
  344. old := os.Stdout
  345. os.Stdout = os.NewFile(0, os.DevNull)
  346. defer func() {
  347. os.Stdout = old
  348. }()
  349. filename, err := fs.TempFilenameWithText("foo")
  350. assert.Nil(t, err)
  351. logger, err := NewLogger(filename, new(SizeLimitRotateRule), true)
  352. assert.Nil(t, err)
  353. if len(filename) > 0 {
  354. defer os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
  355. }
  356. logger.maybeCompressFile(filename)
  357. _, err = os.Stat(filename)
  358. assert.NotNil(t, err)
  359. }
  360. func TestRotateLoggerWithSizeLimitRotateRuleMayCompressFileFailed(t *testing.T) {
  361. old := os.Stdout
  362. os.Stdout = os.NewFile(0, os.DevNull)
  363. defer func() {
  364. os.Stdout = old
  365. }()
  366. filename := stringx.RandId()
  367. logger, err := NewLogger(filename, new(SizeLimitRotateRule), true)
  368. defer os.Remove(filename)
  369. if assert.NoError(t, err) {
  370. assert.NotPanics(t, func() {
  371. logger.maybeCompressFile(stringx.RandId())
  372. })
  373. }
  374. }
  375. func TestRotateLoggerWithSizeLimitRotateRuleRotate(t *testing.T) {
  376. filename, err := fs.TempFilenameWithText("foo")
  377. assert.Nil(t, err)
  378. logger, err := NewLogger(filename, new(SizeLimitRotateRule), true)
  379. assert.Nil(t, err)
  380. if len(filename) > 0 {
  381. defer func() {
  382. os.Remove(logger.getBackupFilename())
  383. os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
  384. }()
  385. }
  386. err = logger.rotate()
  387. switch v := err.(type) {
  388. case *os.LinkError:
  389. // avoid rename error on docker container
  390. assert.Equal(t, syscall.EXDEV, v.Err)
  391. case *os.PathError:
  392. // ignore remove error for tests,
  393. // files are cleaned in GitHub actions.
  394. assert.Equal(t, "remove", v.Op)
  395. default:
  396. assert.Nil(t, err)
  397. }
  398. }
  399. func TestRotateLoggerWithSizeLimitRotateRuleWrite(t *testing.T) {
  400. filename, err := fs.TempFilenameWithText("foo")
  401. assert.Nil(t, err)
  402. rule := new(SizeLimitRotateRule)
  403. logger, err := NewLogger(filename, rule, true)
  404. assert.Nil(t, err)
  405. if len(filename) > 0 {
  406. defer func() {
  407. os.Remove(logger.getBackupFilename())
  408. os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
  409. }()
  410. }
  411. // the following write calls cannot be changed to Write, because of DATA RACE.
  412. logger.write([]byte(`foo`))
  413. rule.rotatedTime = time.Now().Add(-time.Hour * 24).Format(dateFormat)
  414. logger.write([]byte(`bar`))
  415. logger.Close()
  416. logger.write([]byte(`baz`))
  417. }
  418. func TestGzipFile(t *testing.T) {
  419. err := errors.New("any error")
  420. t.Run("gzip file open failed", func(t *testing.T) {
  421. fsys := &fakeFileSystem{
  422. openFn: func(name string) (*os.File, error) {
  423. return nil, err
  424. },
  425. }
  426. assert.ErrorIs(t, err, gzipFile("any", fsys))
  427. assert.False(t, fsys.Removed())
  428. })
  429. t.Run("gzip file create failed", func(t *testing.T) {
  430. fsys := &fakeFileSystem{
  431. createFn: func(name string) (*os.File, error) {
  432. return nil, err
  433. },
  434. }
  435. assert.ErrorIs(t, err, gzipFile("any", fsys))
  436. assert.False(t, fsys.Removed())
  437. })
  438. t.Run("gzip file copy failed", func(t *testing.T) {
  439. fsys := &fakeFileSystem{
  440. copyFn: func(writer io.Writer, reader io.Reader) (int64, error) {
  441. return 0, err
  442. },
  443. }
  444. assert.ErrorIs(t, err, gzipFile("any", fsys))
  445. assert.False(t, fsys.Removed())
  446. })
  447. t.Run("gzip file last close failed", func(t *testing.T) {
  448. var called int32
  449. fsys := &fakeFileSystem{
  450. closeFn: func(closer io.Closer) error {
  451. if atomic.AddInt32(&called, 1) > 2 {
  452. return err
  453. }
  454. return nil
  455. },
  456. }
  457. assert.NoError(t, gzipFile("any", fsys))
  458. assert.True(t, fsys.Removed())
  459. })
  460. t.Run("gzip file remove failed", func(t *testing.T) {
  461. fsys := &fakeFileSystem{
  462. removeFn: func(name string) error {
  463. return err
  464. },
  465. }
  466. assert.Error(t, err, gzipFile("any", fsys))
  467. assert.True(t, fsys.Removed())
  468. })
  469. t.Run("gzip file everything ok", func(t *testing.T) {
  470. fsys := &fakeFileSystem{}
  471. assert.NoError(t, gzipFile("any", fsys))
  472. assert.True(t, fsys.Removed())
  473. })
  474. }
  475. func TestRotateLogger_WithExistingFile(t *testing.T) {
  476. const body = "foo"
  477. filename, err := fs.TempFilenameWithText(body)
  478. assert.Nil(t, err)
  479. if len(filename) > 0 {
  480. defer os.Remove(filename)
  481. }
  482. rule := NewSizeLimitRotateRule(filename, "-", 1, 100, 3, false)
  483. logger, err := NewLogger(filename, rule, false)
  484. assert.Nil(t, err)
  485. assert.Equal(t, int64(len(body)), logger.currentSize)
  486. assert.Nil(t, logger.Close())
  487. }
  488. func BenchmarkRotateLogger(b *testing.B) {
  489. filename := "./test.log"
  490. filename2 := "./test2.log"
  491. dailyRotateRuleLogger, err1 := NewLogger(
  492. filename,
  493. DefaultRotateRule(
  494. filename,
  495. backupFileDelimiter,
  496. 1,
  497. true,
  498. ),
  499. true,
  500. )
  501. if err1 != nil {
  502. b.Logf("Failed to new daily rotate rule logger: %v", err1)
  503. b.FailNow()
  504. }
  505. sizeLimitRotateRuleLogger, err2 := NewLogger(
  506. filename2,
  507. NewSizeLimitRotateRule(
  508. filename,
  509. backupFileDelimiter,
  510. 1,
  511. 100,
  512. 10,
  513. true,
  514. ),
  515. true,
  516. )
  517. if err2 != nil {
  518. b.Logf("Failed to new size limit rotate rule logger: %v", err1)
  519. b.FailNow()
  520. }
  521. defer func() {
  522. dailyRotateRuleLogger.Close()
  523. sizeLimitRotateRuleLogger.Close()
  524. os.Remove(filename)
  525. os.Remove(filename2)
  526. }()
  527. b.Run("daily rotate rule", func(b *testing.B) {
  528. for i := 0; i < b.N; i++ {
  529. dailyRotateRuleLogger.write([]byte("testing\ntesting\n"))
  530. }
  531. })
  532. b.Run("size limit rotate rule", func(b *testing.B) {
  533. for i := 0; i < b.N; i++ {
  534. sizeLimitRotateRuleLogger.write([]byte("testing\ntesting\n"))
  535. }
  536. })
  537. }
  538. type fakeFileSystem struct {
  539. removed int32
  540. closeFn func(closer io.Closer) error
  541. copyFn func(writer io.Writer, reader io.Reader) (int64, error)
  542. createFn func(name string) (*os.File, error)
  543. openFn func(name string) (*os.File, error)
  544. removeFn func(name string) error
  545. }
  546. func (f *fakeFileSystem) Close(closer io.Closer) error {
  547. if f.closeFn != nil {
  548. return f.closeFn(closer)
  549. }
  550. return nil
  551. }
  552. func (f *fakeFileSystem) Copy(writer io.Writer, reader io.Reader) (int64, error) {
  553. if f.copyFn != nil {
  554. return f.copyFn(writer, reader)
  555. }
  556. return 0, nil
  557. }
  558. func (f *fakeFileSystem) Create(name string) (*os.File, error) {
  559. if f.createFn != nil {
  560. return f.createFn(name)
  561. }
  562. return nil, nil
  563. }
  564. func (f *fakeFileSystem) Open(name string) (*os.File, error) {
  565. if f.openFn != nil {
  566. return f.openFn(name)
  567. }
  568. return nil, nil
  569. }
  570. func (f *fakeFileSystem) Remove(name string) error {
  571. atomic.AddInt32(&f.removed, 1)
  572. if f.removeFn != nil {
  573. return f.removeFn(name)
  574. }
  575. return nil
  576. }
  577. func (f *fakeFileSystem) Removed() bool {
  578. return atomic.LoadInt32(&f.removed) > 0
  579. }