uast_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. // +build !disable_babelfish
  2. package uast
  3. import (
  4. "bytes"
  5. "io/ioutil"
  6. "os"
  7. "testing"
  8. "fmt"
  9. "path"
  10. "github.com/gogo/protobuf/proto"
  11. "github.com/stretchr/testify/assert"
  12. "gopkg.in/bblfsh/sdk.v1/uast"
  13. "gopkg.in/src-d/go-git.v4/plumbing"
  14. "gopkg.in/src-d/go-git.v4/plumbing/object"
  15. "gopkg.in/src-d/hercules.v4/internal/core"
  16. "gopkg.in/src-d/hercules.v4/internal/pb"
  17. items "gopkg.in/src-d/hercules.v4/internal/plumbing"
  18. "gopkg.in/src-d/hercules.v4/internal/test"
  19. )
  20. func fixtureUASTExtractor() *Extractor {
  21. exr := Extractor{Endpoint: "0.0.0.0:9432"}
  22. exr.Initialize(test.Repository)
  23. return &exr
  24. }
  25. func TestUASTExtractorMeta(t *testing.T) {
  26. exr := fixtureUASTExtractor()
  27. assert.Equal(t, exr.Name(), "UAST")
  28. assert.Equal(t, len(exr.Provides()), 1)
  29. assert.Equal(t, exr.Provides()[0], DependencyUasts)
  30. assert.Equal(t, len(exr.Requires()), 2)
  31. assert.Equal(t, exr.Requires()[0], items.DependencyTreeChanges)
  32. assert.Equal(t, exr.Requires()[1], items.DependencyBlobCache)
  33. opts := exr.ListConfigurationOptions()
  34. assert.Len(t, opts, 4)
  35. assert.Equal(t, opts[0].Name, ConfigUASTEndpoint)
  36. assert.Equal(t, opts[1].Name, ConfigUASTTimeout)
  37. assert.Equal(t, opts[2].Name, ConfigUASTPoolSize)
  38. assert.Equal(t, opts[3].Name, ConfigUASTFailOnErrors)
  39. feats := exr.Features()
  40. assert.Len(t, feats, 1)
  41. assert.Equal(t, feats[0], FeatureUast)
  42. }
  43. func TestUASTExtractorConfiguration(t *testing.T) {
  44. exr := fixtureUASTExtractor()
  45. facts := map[string]interface{}{}
  46. exr.Configure(facts)
  47. facts[ConfigUASTEndpoint] = "localhost:9432"
  48. facts[ConfigUASTTimeout] = 15
  49. facts[ConfigUASTPoolSize] = 7
  50. facts[ConfigUASTFailOnErrors] = true
  51. exr.Configure(facts)
  52. assert.Equal(t, exr.Endpoint, facts[ConfigUASTEndpoint])
  53. assert.NotNil(t, exr.Context)
  54. assert.Equal(t, exr.PoolSize, facts[ConfigUASTPoolSize])
  55. assert.Equal(t, exr.FailOnErrors, true)
  56. }
  57. func TestUASTExtractorRegistration(t *testing.T) {
  58. summoned := core.Registry.Summon((&Extractor{}).Name())
  59. assert.Len(t, summoned, 1)
  60. assert.Equal(t, summoned[0].Name(), "UAST")
  61. summoned = core.Registry.Summon((&Extractor{}).Provides()[0])
  62. assert.Len(t, summoned, 1)
  63. assert.Equal(t, summoned[0].Name(), "UAST")
  64. }
  65. func TestUASTExtractorConsume(t *testing.T) {
  66. exr := fixtureUASTExtractor()
  67. changes := make(object.Changes, 3)
  68. // 2b1ed978194a94edeabbca6de7ff3b5771d4d665
  69. treeFrom, _ := test.Repository.TreeObject(plumbing.NewHash(
  70. "96c6ece9b2f3c7c51b83516400d278dea5605100"))
  71. treeTo, _ := test.Repository.TreeObject(plumbing.NewHash(
  72. "251f2094d7b523d5bcc60e663b6cf38151bf8844"))
  73. changes[0] = &object.Change{From: object.ChangeEntry{
  74. Name: "analyser.go",
  75. Tree: treeFrom,
  76. TreeEntry: object.TreeEntry{
  77. Name: "analyser.go",
  78. Mode: 0100644,
  79. Hash: plumbing.NewHash("baa64828831d174f40140e4b3cfa77d1e917a2c1"),
  80. },
  81. }, To: object.ChangeEntry{},
  82. }
  83. changes[1] = &object.Change{From: object.ChangeEntry{
  84. Name: "cmd/hercules/main.go",
  85. Tree: treeFrom,
  86. TreeEntry: object.TreeEntry{
  87. Name: "cmd/hercules/main.go",
  88. Mode: 0100644,
  89. Hash: plumbing.NewHash("c29112dbd697ad9b401333b80c18a63951bc18d9"),
  90. },
  91. }, To: object.ChangeEntry{
  92. Name: "cmd/hercules/main.go",
  93. Tree: treeTo,
  94. TreeEntry: object.TreeEntry{
  95. Name: "cmd/hercules/main.go",
  96. Mode: 0100644,
  97. Hash: plumbing.NewHash("f7d918ec500e2f925ecde79b51cc007bac27de72"),
  98. },
  99. },
  100. }
  101. changes[2] = &object.Change{From: object.ChangeEntry{}, To: object.ChangeEntry{
  102. Name: "linux.png",
  103. Tree: treeTo,
  104. TreeEntry: object.TreeEntry{
  105. Name: "linux.png",
  106. Mode: 0100644,
  107. Hash: plumbing.NewHash("81f2b6d1fa5357f90e9dead150cd515720897545"),
  108. },
  109. },
  110. }
  111. cache := map[plumbing.Hash]*object.Blob{}
  112. for _, hash := range []string{
  113. "baa64828831d174f40140e4b3cfa77d1e917a2c1",
  114. "5d78f57d732aed825764347ec6f3ab74d50d0619",
  115. "c29112dbd697ad9b401333b80c18a63951bc18d9",
  116. "f7d918ec500e2f925ecde79b51cc007bac27de72",
  117. "81f2b6d1fa5357f90e9dead150cd515720897545",
  118. } {
  119. cache[plumbing.NewHash(hash)], _ = test.Repository.BlobObject(plumbing.NewHash(hash))
  120. }
  121. deps := map[string]interface{}{}
  122. deps[items.DependencyBlobCache] = cache
  123. deps[items.DependencyTreeChanges] = changes
  124. deps[core.DependencyCommit], _ = test.Repository.CommitObject(
  125. plumbing.NewHash("2b1ed978194a94edeabbca6de7ff3b5771d4d665"))
  126. res, err := exr.Consume(deps)
  127. assert.Len(t, res[DependencyUasts], 1)
  128. assert.Nil(t, err)
  129. res, err = exr.Consume(deps)
  130. assert.Len(t, res[DependencyUasts], 1)
  131. assert.Nil(t, err)
  132. hash := plumbing.NewHash("5d78f57d732aed825764347ec6f3ab74d50d0619")
  133. changes[1] = &object.Change{From: object.ChangeEntry{}, To: object.ChangeEntry{
  134. Name: "labours.py",
  135. Tree: treeTo,
  136. TreeEntry: object.TreeEntry{
  137. Name: "labours.py",
  138. Mode: 0100644,
  139. Hash: hash,
  140. },
  141. },
  142. }
  143. deps[items.DependencyTreeChanges] = changes[:2]
  144. res, err = exr.Consume(deps)
  145. assert.Nil(t, err)
  146. uasts := res[DependencyUasts].(map[plumbing.Hash]*uast.Node)
  147. assert.Equal(t, len(uasts), 1)
  148. assert.Equal(t, len(uasts[hash].Children), 24)
  149. }
  150. func TestUASTExtractorFork(t *testing.T) {
  151. exr1 := fixtureUASTExtractor()
  152. clones := exr1.Fork(1)
  153. assert.Len(t, clones, 1)
  154. exr2 := clones[0].(*Extractor)
  155. assert.True(t, exr1 == exr2)
  156. exr1.Merge([]core.PipelineItem{exr2})
  157. }
  158. func fixtureUASTChanges() *Changes {
  159. ch := Changes{}
  160. ch.Configure(nil)
  161. ch.Initialize(test.Repository)
  162. return &ch
  163. }
  164. func TestUASTChangesMeta(t *testing.T) {
  165. ch := fixtureUASTChanges()
  166. assert.Equal(t, ch.Name(), "UASTChanges")
  167. assert.Equal(t, len(ch.Provides()), 1)
  168. assert.Equal(t, ch.Provides()[0], DependencyUastChanges)
  169. assert.Equal(t, len(ch.Requires()), 2)
  170. assert.Equal(t, ch.Requires()[0], DependencyUasts)
  171. assert.Equal(t, ch.Requires()[1], items.DependencyTreeChanges)
  172. opts := ch.ListConfigurationOptions()
  173. assert.Len(t, opts, 0)
  174. feats := ch.Features()
  175. assert.Len(t, feats, 1)
  176. assert.Equal(t, feats[0], FeatureUast)
  177. }
  178. func TestUASTChangesRegistration(t *testing.T) {
  179. summoned := core.Registry.Summon((&Changes{}).Name())
  180. assert.Len(t, summoned, 1)
  181. assert.Equal(t, summoned[0].Name(), "UASTChanges")
  182. summoned = core.Registry.Summon((&Changes{}).Provides()[0])
  183. assert.True(t, len(summoned) >= 1)
  184. matched := false
  185. for _, tp := range summoned {
  186. matched = matched || tp.Name() == "UASTChanges"
  187. }
  188. assert.True(t, matched)
  189. }
  190. func TestUASTChangesConsume(t *testing.T) {
  191. var uastsArray []*uast.Node
  192. uasts := map[plumbing.Hash]*uast.Node{}
  193. hash := plumbing.NewHash("291286b4ac41952cbd1389fda66420ec03c1a9fe")
  194. uasts[hash] = &uast.Node{}
  195. uasts[hash].InternalType = "uno"
  196. uastsArray = append(uastsArray, uasts[hash])
  197. hash = plumbing.NewHash("c29112dbd697ad9b401333b80c18a63951bc18d9")
  198. uasts[hash] = &uast.Node{}
  199. uasts[hash].InternalType = "dos"
  200. uastsArray = append(uastsArray, uasts[hash])
  201. hash = plumbing.NewHash("baa64828831d174f40140e4b3cfa77d1e917a2c1")
  202. uasts[hash] = &uast.Node{}
  203. uasts[hash].InternalType = "tres"
  204. uastsArray = append(uastsArray, uasts[hash])
  205. hash = plumbing.NewHash("dc248ba2b22048cc730c571a748e8ffcf7085ab9")
  206. uasts[hash] = &uast.Node{}
  207. uasts[hash].InternalType = "quatro"
  208. uastsArray = append(uastsArray, uasts[hash])
  209. changes := make(object.Changes, 3)
  210. treeFrom, _ := test.Repository.TreeObject(plumbing.NewHash(
  211. "a1eb2ea76eb7f9bfbde9b243861474421000eb96"))
  212. treeTo, _ := test.Repository.TreeObject(plumbing.NewHash(
  213. "994eac1cd07235bb9815e547a75c84265dea00f5"))
  214. changes[0] = &object.Change{From: object.ChangeEntry{
  215. Name: "analyser.go",
  216. Tree: treeFrom,
  217. TreeEntry: object.TreeEntry{
  218. Name: "analyser.go",
  219. Mode: 0100644,
  220. Hash: plumbing.NewHash("dc248ba2b22048cc730c571a748e8ffcf7085ab9"),
  221. },
  222. }, To: object.ChangeEntry{
  223. Name: "analyser.go",
  224. Tree: treeTo,
  225. TreeEntry: object.TreeEntry{
  226. Name: "analyser.go",
  227. Mode: 0100644,
  228. Hash: plumbing.NewHash("baa64828831d174f40140e4b3cfa77d1e917a2c1"),
  229. },
  230. }}
  231. changes[1] = &object.Change{From: object.ChangeEntry{}, To: object.ChangeEntry{
  232. Name: "cmd/hercules/main.go",
  233. Tree: treeTo,
  234. TreeEntry: object.TreeEntry{
  235. Name: "cmd/hercules/main.go",
  236. Mode: 0100644,
  237. Hash: plumbing.NewHash("c29112dbd697ad9b401333b80c18a63951bc18d9"),
  238. },
  239. },
  240. }
  241. changes[2] = &object.Change{To: object.ChangeEntry{}, From: object.ChangeEntry{
  242. Name: ".travis.yml",
  243. Tree: treeTo,
  244. TreeEntry: object.TreeEntry{
  245. Name: ".travis.yml",
  246. Mode: 0100644,
  247. Hash: plumbing.NewHash("291286b4ac41952cbd1389fda66420ec03c1a9fe"),
  248. },
  249. },
  250. }
  251. deps := map[string]interface{}{}
  252. deps[DependencyUasts] = uasts
  253. deps[items.DependencyTreeChanges] = changes
  254. ch := fixtureUASTChanges()
  255. ch.cache[changes[0].From.TreeEntry.Hash] = uastsArray[3]
  256. ch.cache[changes[2].From.TreeEntry.Hash] = uastsArray[0]
  257. resultMap, err := ch.Consume(deps)
  258. assert.Nil(t, err)
  259. result := resultMap[DependencyUastChanges].([]Change)
  260. assert.Len(t, result, 3)
  261. assert.Equal(t, result[0].Change, changes[0])
  262. assert.Equal(t, result[0].Before, uastsArray[3])
  263. assert.Equal(t, result[0].After, uastsArray[2])
  264. assert.Equal(t, result[1].Change, changes[1])
  265. assert.Nil(t, result[1].Before)
  266. assert.Equal(t, result[1].After, uastsArray[1])
  267. assert.Equal(t, result[2].Change, changes[2])
  268. assert.Equal(t, result[2].Before, uastsArray[0])
  269. assert.Nil(t, result[2].After)
  270. }
  271. func TestUASTChangesFork(t *testing.T) {
  272. changes1 := fixtureUASTChanges()
  273. changes1.cache[plumbing.ZeroHash] = nil
  274. clones := changes1.Fork(1)
  275. assert.Len(t, clones, 1)
  276. changes2 := clones[0].(*Changes)
  277. assert.False(t, changes1 == changes2)
  278. assert.Equal(t, changes1.cache, changes2.cache)
  279. delete(changes1.cache, plumbing.ZeroHash)
  280. assert.Len(t, changes2.cache, 1)
  281. changes1.Merge([]core.PipelineItem{changes2})
  282. }
  283. func fixtureUASTChangesSaver() *ChangesSaver {
  284. ch := ChangesSaver{}
  285. ch.Initialize(test.Repository)
  286. return &ch
  287. }
  288. func TestUASTChangesSaverMeta(t *testing.T) {
  289. chs := fixtureUASTChangesSaver()
  290. assert.Equal(t, chs.Name(), "UASTChangesSaver")
  291. assert.True(t, len(chs.Description()) > 0)
  292. assert.Equal(t, len(chs.Provides()), 0)
  293. assert.Equal(t, len(chs.Requires()), 1)
  294. assert.Equal(t, chs.Requires()[0], DependencyUastChanges)
  295. opts := chs.ListConfigurationOptions()
  296. assert.Len(t, opts, 1)
  297. assert.Equal(t, opts[0].Name, ConfigUASTChangesSaverOutputPath)
  298. feats := chs.Features()
  299. assert.Len(t, feats, 1)
  300. assert.Equal(t, feats[0], FeatureUast)
  301. assert.Equal(t, chs.Flag(), "dump-uast-changes")
  302. }
  303. func TestUASTChangesSaverConfiguration(t *testing.T) {
  304. facts := map[string]interface{}{}
  305. chs := fixtureUASTChangesSaver()
  306. chs.Configure(facts)
  307. assert.Empty(t, chs.OutputPath)
  308. facts[ConfigUASTChangesSaverOutputPath] = "libre"
  309. chs.Configure(facts)
  310. assert.Equal(t, chs.OutputPath, "libre")
  311. }
  312. func TestUASTChangesSaverRegistration(t *testing.T) {
  313. summoned := core.Registry.Summon((&ChangesSaver{}).Name())
  314. assert.Len(t, summoned, 1)
  315. assert.Equal(t, summoned[0].Name(), "UASTChangesSaver")
  316. leaves := core.Registry.GetLeaves()
  317. matched := false
  318. for _, tp := range leaves {
  319. if tp.Flag() == (&ChangesSaver{}).Flag() {
  320. matched = true
  321. break
  322. }
  323. }
  324. assert.True(t, matched)
  325. }
  326. func TestUASTChangesSaverPayload(t *testing.T) {
  327. chs := fixtureUASTChangesSaver()
  328. deps := map[string]interface{}{}
  329. changes := make([]Change, 1)
  330. deps[DependencyUastChanges] = changes
  331. deps[core.DependencyCommit], _ = test.Repository.CommitObject(
  332. plumbing.NewHash("2b1ed978194a94edeabbca6de7ff3b5771d4d665"))
  333. treeFrom, _ := test.Repository.TreeObject(plumbing.NewHash(
  334. "a1eb2ea76eb7f9bfbde9b243861474421000eb96"))
  335. treeTo, _ := test.Repository.TreeObject(plumbing.NewHash(
  336. "994eac1cd07235bb9815e547a75c84265dea00f5"))
  337. changes[0] = Change{Before: &uast.Node{}, After: &uast.Node{},
  338. Change: &object.Change{From: object.ChangeEntry{
  339. Name: "analyser.go",
  340. Tree: treeFrom,
  341. TreeEntry: object.TreeEntry{
  342. Name: "analyser.go",
  343. Mode: 0100644,
  344. Hash: plumbing.NewHash("dc248ba2b22048cc730c571a748e8ffcf7085ab9"),
  345. },
  346. }, To: object.ChangeEntry{
  347. Name: "analyser.go",
  348. Tree: treeTo,
  349. TreeEntry: object.TreeEntry{
  350. Name: "analyser.go",
  351. Mode: 0100644,
  352. Hash: plumbing.NewHash("334cde09da4afcb74f8d2b3e6fd6cce61228b485"),
  353. },
  354. }}}
  355. chs.Consume(deps)
  356. res := chs.Finalize()
  357. tmpdir, err := ioutil.TempDir("", "hercules-test-")
  358. assert.Nil(t, err)
  359. defer os.RemoveAll(tmpdir)
  360. chs.OutputPath = tmpdir
  361. buffer := &bytes.Buffer{}
  362. chs.Serialize(res, true, buffer)
  363. pbResults := &pb.UASTChangesSaverResults{}
  364. proto.Unmarshal(buffer.Bytes(), pbResults)
  365. assert.Len(t, pbResults.Changes, 1)
  366. assert.Equal(t, pbResults.Changes[0].FileName, "analyser.go")
  367. assert.Equal(t, pbResults.Changes[0].SrcAfter,
  368. path.Join(tmpdir, "0_0_after_334cde09da4afcb74f8d2b3e6fd6cce61228b485.src"))
  369. assert.Equal(t, pbResults.Changes[0].SrcBefore,
  370. path.Join(tmpdir, "0_0_before_dc248ba2b22048cc730c571a748e8ffcf7085ab9.src"))
  371. assert.Equal(t, pbResults.Changes[0].UastAfter,
  372. path.Join(tmpdir, "0_0_after_334cde09da4afcb74f8d2b3e6fd6cce61228b485.pb"))
  373. assert.Equal(t, pbResults.Changes[0].UastBefore,
  374. path.Join(tmpdir, "0_0_before_dc248ba2b22048cc730c571a748e8ffcf7085ab9.pb"))
  375. checkFiles := func() {
  376. files, err := ioutil.ReadDir(tmpdir)
  377. assert.Nil(t, err)
  378. assert.Len(t, files, 4)
  379. names := map[string]int{
  380. "0_0_after_334cde09da4afcb74f8d2b3e6fd6cce61228b485.src": 1,
  381. "0_0_before_dc248ba2b22048cc730c571a748e8ffcf7085ab9.src": 1,
  382. "0_0_after_334cde09da4afcb74f8d2b3e6fd6cce61228b485.pb": 1,
  383. "0_0_before_dc248ba2b22048cc730c571a748e8ffcf7085ab9.pb": 1,
  384. }
  385. matches := 0
  386. for _, fi := range files {
  387. matches += names[fi.Name()]
  388. os.Remove(fi.Name())
  389. }
  390. assert.Equal(t, matches, len(names))
  391. }
  392. checkFiles()
  393. buffer.Truncate(0)
  394. chs.Serialize(res, false, buffer)
  395. assert.Equal(t, buffer.String(), fmt.Sprintf(` - {file: analyser.go, src0: %s/0_0_before_dc248ba2b22048cc730c571a748e8ffcf7085ab9.src, src1: %s/0_0_after_334cde09da4afcb74f8d2b3e6fd6cce61228b485.src, uast0: %s/0_0_before_dc248ba2b22048cc730c571a748e8ffcf7085ab9.pb, uast1: %s/0_0_after_334cde09da4afcb74f8d2b3e6fd6cce61228b485.pb}
  396. `, tmpdir, tmpdir, tmpdir, tmpdir))
  397. checkFiles()
  398. }
  399. func TestUASTChangesSaverConsumeMerge(t *testing.T) {
  400. chs := fixtureUASTChangesSaver()
  401. deps := map[string]interface{}{}
  402. changes := make([]Change, 1)
  403. deps[DependencyUastChanges] = changes
  404. deps[core.DependencyCommit], _ = test.Repository.CommitObject(
  405. plumbing.NewHash("2b1ed978194a94edeabbca6de7ff3b5771d4d665"))
  406. treeFrom, _ := test.Repository.TreeObject(plumbing.NewHash(
  407. "a1eb2ea76eb7f9bfbde9b243861474421000eb96"))
  408. treeTo, _ := test.Repository.TreeObject(plumbing.NewHash(
  409. "994eac1cd07235bb9815e547a75c84265dea00f5"))
  410. changes[0] = Change{Before: &uast.Node{}, After: &uast.Node{},
  411. Change: &object.Change{From: object.ChangeEntry{
  412. Name: "analyser.go",
  413. Tree: treeFrom,
  414. TreeEntry: object.TreeEntry{
  415. Name: "analyser.go",
  416. Mode: 0100644,
  417. Hash: plumbing.NewHash("dc248ba2b22048cc730c571a748e8ffcf7085ab9"),
  418. },
  419. }, To: object.ChangeEntry{
  420. Name: "analyser.go",
  421. Tree: treeTo,
  422. TreeEntry: object.TreeEntry{
  423. Name: "analyser.go",
  424. Mode: 0100644,
  425. Hash: plumbing.NewHash("334cde09da4afcb74f8d2b3e6fd6cce61228b485"),
  426. },
  427. }}}
  428. deps[core.DependencyCommit], _ = test.Repository.CommitObject(
  429. plumbing.NewHash("cce947b98a050c6d356bc6ba95030254914027b1"))
  430. chs.Consume(deps)
  431. assert.Len(t, chs.result, 1)
  432. chs.Consume(deps)
  433. assert.Len(t, chs.result, 2)
  434. deps[core.DependencyCommit], _ = test.Repository.CommitObject(
  435. plumbing.NewHash("dd9dd084d5851d7dc4399fc7dbf3d8292831ebc5"))
  436. chs.Consume(deps)
  437. assert.Len(t, chs.result, 3)
  438. chs.Consume(deps)
  439. assert.Len(t, chs.result, 3)
  440. }
  441. func TestUASTChangesSaverFork(t *testing.T) {
  442. saver1 := fixtureUASTChangesSaver()
  443. clones := saver1.Fork(1)
  444. assert.Len(t, clones, 1)
  445. saver2 := clones[0].(*ChangesSaver)
  446. assert.True(t, saver1 == saver2)
  447. saver1.Merge([]core.PipelineItem{saver2})
  448. }