github.com/loafoe/cli@v7.1.0+incompatible/integration/v7/isolated/scale_command_test.go (about) 1 package isolated 2 3 import ( 4 "fmt" 5 "strings" 6 7 . "code.cloudfoundry.org/cli/cf/util/testhelpers/matchers" 8 9 "code.cloudfoundry.org/cli/integration/helpers" 10 . "github.com/onsi/ginkgo" 11 . "github.com/onsi/gomega" 12 . "github.com/onsi/gomega/gbytes" 13 . "github.com/onsi/gomega/gexec" 14 ) 15 16 var _ = Describe("scale command", func() { 17 var ( 18 orgName string 19 spaceName string 20 appName string 21 userName string 22 ) 23 24 BeforeEach(func() { 25 orgName = helpers.NewOrgName() 26 spaceName = helpers.NewSpaceName() 27 appName = helpers.PrefixedRandomName("app") 28 userName, _ = helpers.GetCredentials() 29 }) 30 31 Describe("help", func() { 32 When("--help flag is set", func() { 33 It("appears in cf help -a", func() { 34 session := helpers.CF("help", "-a") 35 Eventually(session).Should(Exit(0)) 36 Expect(session).To(HaveCommandInCategoryWithDescription("scale", "APPS", "Change or view the instance count, disk space limit, and memory limit for an app")) 37 }) 38 39 It("displays command usage to output", func() { 40 session := helpers.CF("scale", "--help") 41 42 Eventually(session).Should(Say("NAME:")) 43 Eventually(session).Should(Say("scale - Change or view the instance count, disk space limit, and memory limit for an app")) 44 45 Eventually(session).Should(Say("USAGE:")) 46 Eventually(session).Should(Say(`cf scale APP_NAME \[--process PROCESS\] \[-i INSTANCES\] \[-k DISK\] \[-m MEMORY\]`)) 47 Eventually(session).Should(Say("Modifying the app's disk or memory will cause the app to restart.")) 48 49 Eventually(session).Should(Say("OPTIONS:")) 50 Eventually(session).Should(Say(`-f\s+Force restart of app without prompt`)) 51 Eventually(session).Should(Say(`-i\s+Number of instances`)) 52 Eventually(session).Should(Say(`-k\s+Disk limit \(e\.g\. 256M, 1024M, 1G\)`)) 53 Eventually(session).Should(Say(`-m\s+Memory limit \(e\.g\. 256M, 1024M, 1G\)`)) 54 Eventually(session).Should(Say(`--process\s+App process to scale \(Default: web\)`)) 55 56 Eventually(session).Should(Say("ENVIRONMENT:")) 57 Eventually(session).Should(Say(`CF_STARTUP_TIMEOUT=5\s+Max wait time for app instance startup, in minutes`)) 58 59 Eventually(session).Should(Exit(0)) 60 }) 61 }) 62 }) 63 64 When("the environment is not setup correctly", func() { 65 It("fails with the appropriate errors", func() { 66 helpers.CheckEnvironmentTargetedCorrectly(true, true, ReadOnlyOrg, "scale", appName) 67 }) 68 }) 69 70 When("the environment is set up correctly", func() { 71 BeforeEach(func() { 72 helpers.SetupCF(orgName, spaceName) 73 }) 74 75 AfterEach(func() { 76 helpers.QuickDeleteOrg(orgName) 77 }) 78 79 When("the app name is not provided", func() { 80 It("tells the user that the app name is required, prints help text, and exits 1", func() { 81 session := helpers.CF("scale") 82 83 Eventually(session.Err).Should(Say("Incorrect Usage: the required argument `APP_NAME` was not provided")) 84 Eventually(session).Should(Say("NAME:")) 85 Eventually(session).Should(Exit(1)) 86 }) 87 }) 88 89 When("the app does not exist", func() { 90 It("displays app not found and exits 1", func() { 91 invalidAppName := "invalid-app-name" 92 session := helpers.CF("scale", invalidAppName) 93 Eventually(session.Err).Should(Say("App '%s' not found", invalidAppName)) 94 Eventually(session).Should(Say("FAILED")) 95 Eventually(session).Should(Exit(1)) 96 }) 97 }) 98 99 When("the app exists", func() { 100 BeforeEach(func() { 101 helpers.WithProcfileApp(func(appDir string) { 102 Eventually(helpers.CustomCF(helpers.CFEnv{WorkingDirectory: appDir}, "push", appName)).Should(Exit(0)) 103 }) 104 }) 105 106 When("scale option flags are not provided", func() { 107 It("displays the current scale properties for all processes", func() { 108 session := helpers.CF("scale", appName) 109 110 Eventually(session).Should(Say(`Showing current scale of app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName)) 111 Consistently(session).ShouldNot(Say("Scaling")) 112 Consistently(session).ShouldNot(Say("This will cause the app to restart")) 113 Consistently(session).ShouldNot(Say("Stopping")) 114 Consistently(session).ShouldNot(Say("Starting")) 115 Consistently(session).ShouldNot(Say("Waiting")) 116 Eventually(session).Should(Say(`name:\s+%s`, appName)) 117 Eventually(session).Should(Say(`requested state:\s+started`)) 118 Eventually(session).Should(Exit(0)) 119 120 appTable := helpers.ParseV3AppProcessTable(session.Out.Contents()) 121 Expect(len(appTable.Processes)).To(Equal(2)) 122 123 processSummary := appTable.Processes[0] 124 Expect(processSummary.Type).To(Equal("web")) 125 Expect(processSummary.InstanceCount).To(Equal("1/1")) 126 127 instanceSummary := processSummary.Instances[0] 128 Expect(instanceSummary.Memory).To(MatchRegexp(`\d+(\.\d+)?[KMG]? of \d+[KMG]`)) 129 Expect(instanceSummary.Disk).To(MatchRegexp(`\d+(\.\d+)?[KMG]? of \d+[KMG]`)) 130 131 Expect(appTable.Processes[1].Type).To(Equal("console")) 132 Expect(appTable.Processes[1].InstanceCount).To(Equal("0/0")) 133 }) 134 }) 135 136 When("only one scale option flag is provided", func() { 137 When("scaling the number of instances", func() { 138 It("Scales to the correct number of instances", func() { 139 By("Verifying we start with one instance") 140 session := helpers.CF("scale", appName) 141 Eventually(session).Should(Exit(0)) 142 143 appTable := helpers.ParseV3AppProcessTable(session.Out.Contents()) 144 Expect(appTable.Processes).To(HaveLen(2)) 145 146 processSummary := appTable.Processes[0] 147 Expect(processSummary.Type).To(Equal("web")) 148 Expect(processSummary.InstanceCount).To(Equal("1/1")) 149 150 By("then scaling to 3 instances") 151 session = helpers.CF("scale", appName, "-i", "3") 152 Eventually(session).Should(Say(`Scaling app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName)) 153 Consistently(session).ShouldNot(Say("This will cause the app to restart")) 154 Consistently(session).ShouldNot(Say("Stopping")) 155 Consistently(session).ShouldNot(Say("Starting")) 156 Eventually(session).Should(Exit(0)) 157 158 updatedAppTable := helpers.ParseV3AppProcessTable(session.Out.Contents()) 159 Expect(updatedAppTable.Processes).To(HaveLen(2)) 160 161 processSummary = updatedAppTable.Processes[0] 162 instanceSummary := processSummary.Instances[0] 163 Expect(processSummary.Type).To(Equal("web")) 164 Expect(processSummary.InstanceCount).To(MatchRegexp(`\d/3`)) 165 Expect(instanceSummary.Disk).To(MatchRegexp(`\d+(\.\d+)?[KMG]? of \d+[KMG]`)) 166 Expect(instanceSummary.Memory).To(MatchRegexp(`\d+(\.\d+)?[KMG]? of \d+[KMG]`)) 167 }) 168 }) 169 170 When("Scaling the memory", func() { 171 It("scales memory to 64M", func() { 172 buffer := NewBuffer() 173 _, err := buffer.Write([]byte("y\n")) 174 Expect(err).ToNot(HaveOccurred()) 175 session := helpers.CFWithStdin(buffer, "scale", appName, "-m", "64M") 176 Eventually(session).Should(Say(`Scaling app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName)) 177 Eventually(session).Should(Say(`This will cause the app to restart\. Are you sure you want to scale %s\? \[yN\]:`, appName)) 178 Eventually(session).Should(Say(`Stopping app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName)) 179 Eventually(session).Should(Say(`Starting app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName)) 180 Eventually(session).Should(Exit(0)) 181 182 updatedAppTable := helpers.ParseV3AppProcessTable(session.Out.Contents()) 183 Expect(updatedAppTable.Processes).To(HaveLen(2)) 184 185 processSummary := updatedAppTable.Processes[0] 186 instanceSummary := processSummary.Instances[0] 187 Expect(processSummary.Type).To(Equal("web")) 188 Expect(processSummary.InstanceCount).To(MatchRegexp(`\d/1`)) 189 Expect(instanceSummary.Memory).To(MatchRegexp(`\d+(\.\d+)?[KMG]? of 64M`)) 190 Expect(instanceSummary.Disk).To(MatchRegexp(`\d+(\.\d+)?[KMG]? of \d+[KMG]`)) 191 }) 192 193 When("-f flag provided", func() { 194 It("scales without prompt", func() { 195 session := helpers.CF("scale", appName, "-m", "64M", "-f") 196 Eventually(session).Should(Say("Scaling app %s in org %s / space %s as %s...", appName, orgName, spaceName, userName)) 197 Eventually(session).Should(Exit(0)) 198 199 updatedAppTable := helpers.ParseV3AppProcessTable(session.Out.Contents()) 200 Expect(updatedAppTable.Processes).To(HaveLen(2)) 201 202 processSummary := updatedAppTable.Processes[0] 203 instanceSummary := processSummary.Instances[0] 204 Expect(processSummary.Type).To(Equal("web")) 205 Expect(processSummary.InstanceCount).To(MatchRegexp(`\d/1`)) 206 Expect(instanceSummary.Memory).To(MatchRegexp(`\d+(\.\d+)?[KMG]? of 64M`)) 207 Expect(instanceSummary.Disk).To(MatchRegexp(`\d+(\.\d+)?[KMG]? of \d+[KMG]`)) 208 }) 209 }) 210 }) 211 212 When("Scaling the disk space", func() { 213 It("scales disk to 512M", func() { 214 buffer := NewBuffer() 215 _, err := buffer.Write([]byte("y\n")) 216 Expect(err).ToNot(HaveOccurred()) 217 session := helpers.CFWithStdin(buffer, "scale", appName, "-k", "512M") 218 Eventually(session).Should(Say(`Scaling app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName)) 219 Eventually(session).Should(Say(`This will cause the app to restart\. Are you sure you want to scale %s\? \[yN\]:`, appName)) 220 Eventually(session).Should(Say(`Stopping app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName)) 221 Eventually(session).Should(Say(`Starting app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName)) 222 Eventually(session).Should(Say(`Instances starting\.\.\.`)) 223 Eventually(session).Should(Exit(0)) 224 225 updatedAppTable := helpers.ParseV3AppProcessTable(session.Out.Contents()) 226 Expect(updatedAppTable.Processes).To(HaveLen(2)) 227 228 processSummary := updatedAppTable.Processes[0] 229 instanceSummary := processSummary.Instances[0] 230 Expect(processSummary.Type).To(Equal("web")) 231 Expect(processSummary.InstanceCount).To(MatchRegexp(`\d/1`)) 232 Expect(instanceSummary.Memory).To(MatchRegexp(`\d+(\.\d+)?[KMG]? of \d+[KMG]`)) 233 Expect(instanceSummary.Disk).To(MatchRegexp(`\d+(\.\d+)?[KMG]? of 512M`)) 234 }) 235 236 When("-f flag provided", func() { 237 It("scales without prompt", func() { 238 session := helpers.CF("scale", appName, "-k", "512M", "-f") 239 Eventually(session).Should(Say("Scaling app %s in org %s / space %s as %s...", appName, orgName, spaceName, userName)) 240 Eventually(session).Should(Exit(0)) 241 242 updatedAppTable := helpers.ParseV3AppProcessTable(session.Out.Contents()) 243 Expect(updatedAppTable.Processes).To(HaveLen(2)) 244 245 processSummary := updatedAppTable.Processes[0] 246 instanceSummary := processSummary.Instances[0] 247 Expect(processSummary.Type).To(Equal("web")) 248 Expect(processSummary.InstanceCount).To(MatchRegexp(`\d/1`)) 249 Expect(instanceSummary.Memory).To(MatchRegexp(`\d+(\.\d+)?[KMG]? of \d+[KMG]`)) 250 Expect(instanceSummary.Disk).To(MatchRegexp(`\d+(\.\d+)?[KMG]? of 512M`)) 251 }) 252 }) 253 }) 254 255 When("Scaling to 0 instances", func() { 256 It("scales to 0 instances", func() { 257 session := helpers.CF("scale", appName, "-i", "0") 258 Eventually(session).Should(Say(`Scaling app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName)) 259 Consistently(session).ShouldNot(Say(`This will cause the app to restart|Stopping|Starting`)) 260 Eventually(session).Should(Exit(0)) 261 updatedAppTable := helpers.ParseV3AppProcessTable(session.Out.Contents()) 262 Expect(updatedAppTable.Processes[0].InstanceCount).To(Equal("0/0")) 263 Expect(updatedAppTable.Processes[0].Instances).To(BeEmpty()) 264 }) 265 }) 266 267 When("the user chooses not to restart the app", func() { 268 It("cancels the scale", func() { 269 buffer := NewBuffer() 270 _, err := buffer.Write([]byte("n\n")) 271 Expect(err).ToNot(HaveOccurred()) 272 session := helpers.CFWithStdin(buffer, "scale", appName, "-i", "2", "-k", "90M") 273 Eventually(session).Should(Say("This will cause the app to restart")) 274 Consistently(session).ShouldNot(Say("Stopping")) 275 Consistently(session).ShouldNot(Say("Starting")) 276 Eventually(session).Should(Say("Scaling cancelled")) 277 Consistently(session).ShouldNot(Say(`Waiting for app to start\.\.\.`)) 278 Eventually(session).Should(Exit(0)) 279 280 appTable := helpers.ParseV3AppProcessTable(session.Out.Contents()) 281 Expect(appTable.Processes).To(BeEmpty()) 282 }) 283 }) 284 }) 285 286 When("all scale option flags are provided", func() { 287 When("the app starts successfully", func() { 288 It("scales the app accordingly", func() { 289 buffer := NewBuffer() 290 _, err := buffer.Write([]byte("y\n")) 291 Expect(err).ToNot(HaveOccurred()) 292 session := helpers.CFWithStdin(buffer, "scale", appName, "-i", "2", "-k", "512M", "-m", "60M") 293 Eventually(session).Should(Say(`Scaling app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName)) 294 Eventually(session).Should(Say(`This will cause the app to restart\. Are you sure you want to scale %s\? \[yN\]:`, appName)) 295 Eventually(session).Should(Say(`Stopping app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName)) 296 Eventually(session).Should(Say(`Starting app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName)) 297 Eventually(session).Should(Exit(0)) 298 299 appTable := helpers.ParseV3AppProcessTable(session.Out.Contents()) 300 Expect(appTable.Processes).To(HaveLen(2)) 301 302 processSummary := appTable.Processes[0] 303 instanceSummary := processSummary.Instances[0] 304 Expect(processSummary.Type).To(Equal("web")) 305 Expect(processSummary.InstanceCount).To(MatchRegexp(`\d/2`)) 306 Expect(instanceSummary.State).To(MatchRegexp(`running|starting`)) 307 Expect(instanceSummary.Memory).To(MatchRegexp(`\d+(\.\d+)?[KMG]? of 60M`)) 308 Expect(instanceSummary.Disk).To(MatchRegexp(`\d+(\.\d+)?[KMG]? of 512M`)) 309 }) 310 }) 311 312 When("the app does not start successfully", func() { 313 It("scales the app and displays the app summary", func() { 314 buffer := NewBuffer() 315 _, err := buffer.Write([]byte("y\n")) 316 Expect(err).ToNot(HaveOccurred()) 317 session := helpers.CFWithStdin(buffer, "scale", appName, "-i", "2", "-k", "10M", "-m", "6M") 318 Eventually(session).Should(Say(`Scaling app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName)) 319 Eventually(session).Should(Say(`This will cause the app to restart\. Are you sure you want to scale %s\? \[yN\]:`, appName)) 320 Eventually(session).Should(Say(`Stopping app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName)) 321 Eventually(session).Should(Say(`Starting app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName)) 322 Eventually(session).Should(Exit(1)) 323 324 appTable := helpers.ParseV3AppProcessTable(session.Out.Contents()) 325 Expect(appTable.Processes).To(HaveLen(2)) 326 327 processSummary := appTable.Processes[0] 328 instanceSummary := processSummary.Instances[0] 329 Expect(processSummary.Type).To(Equal("web")) 330 Expect(processSummary.InstanceCount).To(MatchRegexp(`\d/2`)) 331 Expect(instanceSummary.State).To(MatchRegexp(`crashed`)) 332 Expect(instanceSummary.Memory).To(MatchRegexp(`\d+(\.\d+)?[KMG]? of 6M`)) 333 Expect(instanceSummary.Disk).To(MatchRegexp(`\d+(\.\d+)?[KMG]? of 10M`)) 334 }) 335 }) 336 }) 337 338 PWhen("the provided scale options are the same as the existing scale properties", func() { 339 var ( 340 session *Session 341 currentInstances string 342 maxMemory string 343 maxDiskSize string 344 ) 345 346 BeforeEach(func() { 347 session = helpers.CF("scale", appName) 348 Eventually(session).Should(Exit(0)) 349 350 appTable := helpers.ParseV3AppProcessTable(session.Out.Contents()) 351 instanceSummary := appTable.Processes[0].Instances[0] 352 currentInstances = string(len(appTable.Processes[0].Instances)) 353 maxMemory = strings.Fields(instanceSummary.Memory)[2] 354 maxDiskSize = strings.Fields(instanceSummary.Disk)[2] 355 }) 356 357 It("the action should be a no-op", func() { 358 session = helpers.CF("scale", appName, "-i", currentInstances, "-m", maxMemory, "-k", maxDiskSize) 359 Eventually(session).Should(Say(`Scaling app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName)) 360 Consistently(session).ShouldNot(Say("This will cause the app to restart")) 361 Consistently(session).ShouldNot(Say("Stopping")) 362 Consistently(session).ShouldNot(Say("Starting")) 363 Consistently(session).ShouldNot(Say("Waiting for app to start")) 364 Eventually(session).Should(Exit(0)) 365 366 appTable := helpers.ParseV3AppProcessTable(session.Out.Contents()) 367 Expect(appTable.Processes).To(HaveLen(1)) 368 369 newProcessSummary := appTable.Processes[0] 370 newInstanceSummary := newProcessSummary.Instances[0] 371 Expect(newProcessSummary.Type).To(Equal("web")) 372 Expect(newProcessSummary.InstanceCount).To(MatchRegexp(fmt.Sprintf(`\d/%s`, currentInstances))) 373 Expect(newInstanceSummary.Memory).To(MatchRegexp(fmt.Sprintf(`\d+(\.\d+)?[KMG]? of %s`, maxMemory))) 374 Expect(newInstanceSummary.Disk).To(MatchRegexp(fmt.Sprintf(`\d+(\.\d+)?[KMG]? of %s`, maxDiskSize))) 375 }) 376 }) 377 378 When("the process flag is provided", func() { 379 It("scales the requested process", func() { 380 session := helpers.CF("scale", appName, "-i", "2", "--process", "console") 381 Eventually(session).Should(Say(`Scaling app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName)) 382 Eventually(session).Should(Exit(0)) 383 384 appTable := helpers.ParseV3AppProcessTable(session.Out.Contents()) 385 Expect(appTable.Processes).To(HaveLen(2)) 386 387 processSummary := appTable.Processes[1] 388 instanceSummary := processSummary.Instances[0] 389 Expect(processSummary.Instances).To(HaveLen(2)) 390 Expect(processSummary.Type).To(Equal("console")) 391 Expect(processSummary.InstanceCount).To(MatchRegexp(`\d/2`)) 392 Expect(instanceSummary.Memory).To(MatchRegexp(`\d+(\.\d+)?[KMG]? of \d+[KMG]`)) 393 Expect(instanceSummary.Disk).To(MatchRegexp(`\d+(\.\d+)?[KMG]? of \d+[KMG]`)) 394 }) 395 }) 396 }) 397 }) 398 399 When("invalid scale option values are provided", func() { 400 When("a negative value is passed to a flag argument", func() { 401 It("outputs an error message to the user, provides help text, and exits 1", func() { 402 session := helpers.CF("scale", "some-app", "-i=-5") 403 Eventually(session.Err).Should(Say(`Incorrect Usage: invalid argument for flag '-i' \(expected int > 0\)`)) 404 Eventually(session).Should(Say("cf scale APP_NAME")) // help 405 Eventually(session).Should(Exit(1)) 406 407 session = helpers.CF("scale", "some-app", "-k=-5") 408 Eventually(session.Err).Should(Say("Byte quantity must be an integer with a unit of measurement like M, MB, G, or GB")) 409 Eventually(session).Should(Say("cf scale APP_NAME")) // help 410 Eventually(session).Should(Exit(1)) 411 412 session = helpers.CF("scale", "some-app", "-m=-5") 413 Eventually(session.Err).Should(Say("Byte quantity must be an integer with a unit of measurement like M, MB, G, or GB")) 414 Eventually(session).Should(Say("cf scale APP_NAME")) // help 415 Eventually(session).Should(Exit(1)) 416 }) 417 }) 418 419 When("a non-integer value is passed to a flag argument", func() { 420 It("outputs an error message to the user, provides help text, and exits 1", func() { 421 session := helpers.CF("scale", "some-app", "-i", "not-an-integer") 422 Eventually(session.Err).Should(Say(`Incorrect Usage: invalid argument for flag '-i' \(expected int > 0\)`)) 423 Eventually(session).Should(Say("cf scale APP_NAME")) // help 424 Eventually(session).Should(Exit(1)) 425 426 session = helpers.CF("scale", "some-app", "-k", "not-an-integer") 427 Eventually(session.Err).Should(Say("Byte quantity must be an integer with a unit of measurement like M, MB, G, or GB")) 428 Eventually(session).Should(Say("cf scale APP_NAME")) // help 429 Eventually(session).Should(Exit(1)) 430 431 session = helpers.CF("scale", "some-app", "-m", "not-an-integer") 432 Eventually(session.Err).Should(Say("Byte quantity must be an integer with a unit of measurement like M, MB, G, or GB")) 433 Eventually(session).Should(Say("cf scale APP_NAME")) // help 434 Eventually(session).Should(Exit(1)) 435 }) 436 }) 437 438 When("the unit of measurement is not provided", func() { 439 It("outputs an error message to the user, provides help text, and exits 1", func() { 440 session := helpers.CF("scale", "some-app", "-k", "9") 441 Eventually(session.Err).Should(Say("Byte quantity must be an integer with a unit of measurement like M, MB, G, or GB")) 442 Eventually(session).Should(Say("cf scale APP_NAME")) // help 443 Eventually(session).Should(Exit(1)) 444 445 session = helpers.CF("scale", "some-app", "-m", "7") 446 Eventually(session.Err).Should(Say("Byte quantity must be an integer with a unit of measurement like M, MB, G, or GB")) 447 Eventually(session).Should(Say("cf scale APP_NAME")) // help 448 Eventually(session).Should(Exit(1)) 449 }) 450 }) 451 }) 452 })